Data Mining and Predictive Analytics

Project title: Exploratory and Predictive Analysis Of Airbnb Data

Market assigned to the team: New York City

“We, the undersigned, certify that the report submitted is our original work; all authors participated in the work in a substansive way; all authors have seen and approved the report as submitted; the text, images, illustrations, and other items included in the manuscript do not carry any ingringement/plagiarism issue upon any existing copyrighted materials.”

Team Member Names of Signed Team Members
Contact Member Chinchwade, Nikita
Team Member 2 Goyal, Pushkar
Team Member 3 Komarraju, Megha
Team Member 4 Naphade, Chinmay
Team Member 5 Saraiya, Parth
Team Member 6 Singh, Prachi

EXECUTIVE SUMMARY:

Airbnbs in New York City have the second highest host profits out of all the cities in the US, with revenues going up to $741,167,296. Average price of a New York Airbnb listing is $157. The market consists of 5 boroughs: Manhattan, Brooklyn, Queens, the Bronx and Staten Island.

GOALS:

  1. To identify the important features that contribute to achieving a high booking rate on Airbnb, with special emphasis on protecting the investor from investing in a property that would yield low bookings when placed on the Airbnb market.
  2. Build an efficient predictive model using the training data to quantify our analysis by using the most appropriate performance metric.

RESEARCH QUESTIONS:

  1. Buying and maintaining properties in NYC is expensive.For a risk averse investor, the costs associated with an unprofitable investment is significant and can incur a greater loss as compared to the cost associated with missing out on a potentially profitable opportunity.Is it possible to minimize the risk associated with falsely classifying a property with a low booking rate as one with a high booking rate ?

  2. In order to achieve a high booking rate, what our the factors that the investor must consider:
    1. Before buying the property?
    2. After buying the property?

METHODOLOGY:

  1. Data Preparation:-
    1. Filtering the data based one the randomcontrol variable and contrasting it with simple string processing on the market variable
    2. Splitting the amenities into dummy columns for indiviual products and services
    3. Removing the ‘$’ symbol from the some of the columns and converting into numeric
    4. Converting host response rate percentages to numeric
    5. Converting discrete variables to factors
    6. Recoding some of the factor variables based on domain knowledge
    7. Dealing with N/A values based on domain knowledge
    8. For property_types with less than a 1000 observations, create another factor level.
    9. Converting text based descriptive columns into factors. The idea behind this was to be able to reduce complexity.This would check the importance of properly describing aspects of the properties to guests.
    10. Specifying missing number of bedrooms and bathrooms as zero
    11. Specifying missing cleaning fee as mean
    12. Splitting the market into boroughs based on zipcodes and neighbourhoods incase of missing values.

We included the data preprocessing from the Kaggle Competition.

dfaTrain <- read_csv("airbnbTrain.csv")
dfaTest <- read_csv("airbnbTest.csv")
#Using string processing to filter New York data
dfaTrainNewYork <- dfaTrain %>% 
  filter(market == "New York")

#Using Random Control variable to filter New York data
dfaTrain<-dfaTrain %>% filter(as.integer(dfaTrain$`{randomControl}`/1000)==116)

dfaTrainDup <- dfaTrain
#They seem to have the same unique states
unique(dfaTrainNewYork$state)
[1] "NY"       "NJ"       "Ny"       NA         "ny"       "New York"
unique(dfaTrain$state)
[1] "NY"       "Ny"       NA         "NJ"       "ny"       "New York"
#However, dfaTrain (filtered with Random Control) has only one New Jersey zipcode
dfaTrainNJ <- dfaTrain %>% filter(state=="NJ") 
unique(dfaTrainNJ$zipcode)
[1] "07093"
#dfaTrainNewYork (filtered with string processing) has many New Jersey zipcodes
dfaTrainNewYorkNJ <- dfaTrainNewYork %>% filter(state=="NJ")
unique(dfaTrainNewYorkNJ$zipcode)
 [1] "07306"      "07302"      "07310"      "07304"      "07307"      "07305"      "07087"      "07311"      "07047"      "07030"     
[11] NA           "07093"      "07002"      "07302-8544"
skim(dfaTrain)
── Data Summary ────────────────────────
                           Values  
Name                       dfaTrain
Number of rows             30359   
Number of columns          66      
_______________________            
Column type frequency:             
  character                31      
  Date                     1       
  logical                  9       
  numeric                  25      
________________________           
Group variables            None    

── Variable type: character ────────────────────────────────────────────────────────────────────────────────────────────────────────────
   skim_variable         n_missing complete_rate   min   max empty n_unique whitespace
 1 access                    14148        0.534      1  1000     0    14370          0
 2 amenities                     0        1          2  1479     0    28141          0
 3 bed_type                      0        1          5    13     0        5          0
 4 cancellation_policy           0        1          6    27     0        6          0
 5 city                         78        0.997      2    29     0      190          0
 6 cleaning_fee               6310        0.792      5     7     0      191          0
 7 description                 648        0.979      1  1000     0    28798          0
 8 extra_people                  0        1          5     7     0       97          0
 9 host_about                12184        0.599      1 10785     0    14129         50
10 host_acceptance_rate        342        0.989      3     3     0        1          0
11 host_location               429        0.986      2   161     0     1146          0
12 host_neighbourhood         4273        0.859      4    44     0      361          0
13 host_response_rate          342        0.989      2     4     0       78          0
14 host_response_time          342        0.989      3    18     0        5          0
15 host_verifications            0        1          2   170     0      471          0
16 house_rules               11632        0.617      1  1000     0    16089          0
17 interaction               12307        0.595      1  1000     0    16117          0
18 market                       61        0.998      4    19     0       10          0
19 monthly_price             27374        0.0983     7    10     0      490          0
20 neighborhood_overview     10544        0.653      1  1000     0    17421          1
21 neighbourhood                 6        1.00       4    29     0      186          0
22 notes                     18052        0.405      1  1000     0    10712          0
23 price                         0        1          5    10     0      579          0
24 property_type                 0        1          3    22     0       34          0
25 room_type                     0        1         10    15     0        4          0
26 security_deposit          10558        0.652      5     9     0      172          0
27 space                      8561        0.718      1  1000     0    20344          0
28 state                         3        1.00       2     8     0        5          0
29 transit                   10417        0.657      1  1000     0    18074          2
30 weekly_price              26918        0.113      6    10     0      478          0
31 zipcode                     284        0.991      5    11     0      213          0

── Variable type: Date ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median     n_unique
1 host_since          342         0.989 2008-09-07 2019-12-03 2015-06-22     3591

── Variable type: logical ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable                    n_missing complete_rate   mean count                 
1 host_has_profile_pic                   342         0.989 0.997  TRU: 29935, FAL: 82   
2 host_identity_verified                 342         0.989 0.461  FAL: 16193, TRU: 13824
3 host_is_superhost                      342         0.989 0.195  FAL: 24172, TRU: 5845 
4 instant_bookable                         0         1     0.379  FAL: 18842, TRU: 11517
5 is_business_travel_ready                 0         1     0      FAL: 30359            
6 is_location_exact                        0         1     0.825  TRU: 25060, FAL: 5299 
7 require_guest_phone_verification         0         1     0.0232 FAL: 29655, TRU: 704  
8 require_guest_profile_picture            0         1     0.0209 FAL: 29723, TRU: 636  
9 requires_license                         0         1     0      FAL: 30359            

── Variable type: numeric ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
   skim_variable               n_missing complete_rate        mean            sd        p0       p25       p50       p75         p100
 1 id                                  0       1       1101021.       58300.     1000009   1050657   1100476   1151846.     1202115  
 2 high_booking_rate                   0       1             0.204        0.403        0         0         0         0            1  
 3 accommodates                        0       1             2.86         1.90         1         2         2         4           22  
 4 availability_30                     0       1             7.10         9.49         0         0         1        13           30  
 5 availability_365                    0       1           113.         136.           0         0        41       228          365  
 6 availability_60                     0       1            19.3         21.5          0         0         8        39           60  
 7 availability_90                     0       1            32.2         34.5          0         0        14        67           90  
 8 bathrooms                          30       0.999         1.15         0.445        0         1         1         1           15.5
 9 bedrooms                           43       0.999         1.18         0.761        0         1         1         1           14  
10 beds                               77       0.997         1.55         1.14         0         1         1         2           40  
11 guests_included                     0       1             1.52         1.17         1         1         1         2           16  
12 host_listings_count               342       0.989        17.6        113.           0         1         1         2         1767  
13 latitude                            0       1            40.7          0.0552      40.5      40.7      40.7      40.8         40.9
14 longitude                           0       1           -74.0          0.0478     -74.2     -74.0     -74.0     -73.9        -73.7
15 maximum_nights                      0       1         72657.    12326035.           1        29      1000      1125   2147483647  
16 minimum_nights                      0       1             7.38        21.1          1         1         2         5         1125  
17 review_scores_accuracy           6673       0.780         9.61         0.860        2         9        10        10           10  
18 review_scores_checkin            6684       0.780         9.74         0.744        2        10        10        10           10  
19 review_scores_cleanliness        6665       0.780         9.27         1.09         2         9        10        10           10  
20 review_scores_communication      6670       0.780         9.74         0.764        2        10        10        10           10  
21 review_scores_location           6684       0.780         9.58         0.765        2         9        10        10           10  
22 review_scores_rating             6651       0.781        93.8          8.80        20        92        96       100          100  
23 review_scores_value              6686       0.780         9.38         0.936        2         9        10        10           10  
24 square_feet                     30147       0.00698     679.         487.           0       322.      700       900         2400  
25 {randomControl}                     0       1        116501.         289.      116000    116251    116502    116748       116999  
   hist 
 1 ▇▇▇▇▇
 2 ▇▁▁▁▂
 3 ▇▁▁▁▁
 4 ▇▂▁▁▁
 5 ▇▁▁▁▃
 6 ▇▁▂▂▂
 7 ▇▁▁▂▃
 8 ▇▁▁▁▁
 9 ▇▁▁▁▁
10 ▇▁▁▁▁
11 ▇▁▁▁▁
12 ▇▁▁▁▁
13 ▁▂▇▃▁
14 ▁▁▇▂▁
15 ▇▁▁▁▁
16 ▇▁▁▁▁
17 ▁▁▁▁▇
18 ▁▁▁▁▇
19 ▁▁▁▁▇
20 ▁▁▁▁▇
21 ▁▁▁▁▇
22 ▁▁▁▁▇
23 ▁▁▁▁▇
24 ▆▇▂▁▁
25 ▇▇▇▇▇
#Splitting amenities
sampledf <- dfaTrain %>% 
  select(id, amenities) %>% 
  mutate(amenities = gsub('[{]', '', (gsub('[}]', '', amenities))))

df1 <- cSplit_e(sampledf, 'amenities', ',', type= 'character', fill=0, drop=TRUE)
names(df1) <-  sub('.*_', '', names(df1))

i <- (colSums(Filter(is.numeric, df1)) > 1000)
df1 <- df1[,i]

df1 <- 
  df1 %>% 
  mutate_at(names(df1)[-1] , ~factor(.))

dfaTrain <- merge(dfaTrain, df1, by.x = "id", by.y = "id")
#Removing the '$' symbol
dfaTrain <-
  dfaTrain %>%
  mutate(
     cleaning_fee = as.numeric(gsub('[$]', '', (gsub('[,]', '', cleaning_fee)))),
    extra_people = as.numeric(gsub('[$]', '', (gsub('[,]', '', extra_people)))),
    price = as.numeric(gsub('[$]', '', (gsub('[,]', '', price)))),
    security_deposit =as.numeric(gsub('[$]', '', (gsub('[,]', '', security_deposit))))
  )
#Converting percentage to numeric 
dfaTrain <-
  dfaTrain %>%
  mutate(host_response_rate = as.numeric(gsub('[%]', '', host_response_rate))) %>%
  mutate(host_response_rate = host_response_rate / 100)
#Converting discrete variables to factors
cols_to_factor <- c(
  "cancellation_policy",
  "host_response_time",
  "property_type",
  "room_type",
  "host_identity_verified",
  "host_is_superhost",
  "instant_bookable",
  "is_location_exact",
  "requires_license",
  "bed_type"
)

dfaTrain <- 
  dfaTrain %>% 
  mutate_at(cols_to_factor, ~factor(.))
#Recoding bed_type factor levels
dfaTrain <- dfaTrain %>%
  mutate(
    bed_type = recode_factor(
      bed_type,
      Airbed = "Not a Real Bed",
      Couch = "Not a Real Bed",
      Futon = "Not a Real Bed",
      `Pull-out Sofa` = "Not a Real Bed"
    ))
#Recoding cancellation_policy factor levels  
dfaTrain <- dfaTrain %>%
  mutate(
    cancellation_policy = recode_factor(
      cancellation_policy,
      luxury_no_refund = "luxury",
      luxury_super_strict_125 = "luxury",
      luxury_super_strict_95 = "luxury",
      luxury_moderate = "luxury",
      strict = "luxury"
    ))

#Removing NAs
dfaTrain <- 
  dfaTrain %>% 
    replace_na(list(host_response_time = "N/A", host_is_superhost = FALSE, host_identity_verified = FALSE))
# Creating other property_type
combinedPropertyType <- pull(dfaTrain %>% 
  group_by(property_type) %>% 
  tally() %>% 
  filter(n < 1000) %>% 
  arrange(n) %>% 
  select(property_type) %>% 
  mutate(property_type = as.character(property_type)), property_type)

dfaTrain <- dfaTrain %>% 
  mutate(property_type = fct_collapse(property_type, Other = combinedPropertyType))
#Converting text based columns into factors
dfaTrain <- dfaTrain %>% 
  mutate(host_about = ifelse(is.na(host_about),"FALSE", "TRUE"))
dfaTrain <- dfaTrain %>% 
  mutate(interaction = ifelse(is.na(interaction),"FALSE", "TRUE"))
dfaTrain <- dfaTrain %>% 
  mutate(neighborhood_overview = ifelse(is.na(neighborhood_overview),"FALSE", "TRUE"))
dfaTrain <- dfaTrain %>% 
  mutate(transit = ifelse(is.na(transit),"FALSE", "TRUE"))

dfaTrain <- dfaTrain %>% 
  mutate(access = ifelse(is.na(access),"FALSE", "TRUE"))

dfaTrain <- dfaTrain %>% 
  mutate(house_rules = ifelse(is.na(house_rules),"FALSE", "TRUE"))


dfaTrain <- dfaTrain %>% 
  mutate(notes = ifelse(is.na(notes),"FALSE", "TRUE"))


dfaTrain <- dfaTrain %>% 
  mutate(space = ifelse(is.na(space),"FALSE", "TRUE"))

cols_to_factor <- c(
  "host_about",
  "space",
  "notes",
  "house_rules",
  "access",
  "transit",
  "neighborhood_overview",
  "interaction"
)

dfaTrain <- 
  dfaTrain %>% 
  mutate_at(cols_to_factor, ~factor(.))
#Imputing NA values
dfaTrain$bedrooms[is.na(dfaTrain$bedrooms)] =0
dfaTrain$bathrooms[is.na(dfaTrain$bathrooms)] =0
dfaTrain <- dfaTrain %>% group_by(market) %>%
mutate(cleaning_fee=ifelse(is.na(cleaning_fee),mean(cleaning_fee,na.rm=TRUE),cleaning_fee)) %>% ungroup()
#Dividing into boroughs
dfaTrain <- dfaTrain %>% mutate(borough=ifelse(substr(dfaTrain$zipcode, 1, 3)  ==100 | substr(dfaTrain$zipcode, 1, 3)  ==101 | substr(dfaTrain$zipcode, 1, 3)  ==102, "Manhattan" , ifelse(substr(dfaTrain$zipcode, 1, 3)  ==112  ,"Brooklyn",
ifelse(substr(dfaTrain$zipcode, 1, 3)  ==111 | substr(dfaTrain$zipcode, 1, 3)  ==113  | substr(dfaTrain$zipcode, 1, 3)  ==114 | substr(dfaTrain$zipcode, 1, 3)  ==110 | substr(dfaTrain$zipcode, 1, 3)  ==116 ,"Queens",
ifelse(substr(dfaTrain$zipcode, 1, 3)  ==103 ,"Staten Island",
ifelse(substr(dfaTrain$zipcode, 1, 3)  ==104  ,"Bronx","NA"))))))
dfTrainNoZip <-dfaTrain %>% filter(is.na(zipcode))
dfTrainNoZip$borough <- dfaTrain$borough[match(dfTrainNoZip$neighbourhood, dfaTrain$neighbourhood)]
final <- merge(dfaTrain, dfTrainNoZip[ , c("id", "borough")], by = "id", all = TRUE)
final$borough <- final$borough.x %?% final$borough.y
final <- final %>% select(-c("borough.x","borough.y"))
dfaTrain <- final %>% filter(borough!="NA")
head(dfaTrain)

Overview of properties across New York split by boroughs

Extracting Subway Data:

dfzipS <- read_csv("NYsubway1.csv")
Parsed with column specification:
cols(
  Line = col_character(),
  Stationname = col_character(),
  latitude = col_double(),
  longitude = col_double()
)
skim(dfzipS)
── Data Summary ────────────────────────
                           Values
Name                       dfzipS
Number of rows             1868  
Number of columns          4     
_______________________          
Column type frequency:           
  character                2     
  numeric                  2     
________________________         
Group variables            None  

── Variable type: character ────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique whitespace
1 Line                  0             1     5    17     0       36          0
2 Stationname           0             1     4    39     0      356          0

── Variable type: numeric ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate  mean     sd    p0   p25   p50   p75  p100 hist 
1 latitude              0             1  40.7 0.0704  40.6  40.7  40.7  40.8  40.9 ▂▅▇▃▂
2 longitude             0             1 -73.9 0.0572 -74.0 -74.0 -74.0 -73.9 -73.8 ▇▆▃▂▁
#Dividing into boroughs
dfaTrainDup <- dfaTrainDup %>% mutate(borough=ifelse(substr(dfaTrainDup$zipcode, 1, 3)  ==100 | substr(dfaTrainDup$zipcode, 1, 3)  ==101 | substr(dfaTrainDup$zipcode, 1, 3)  ==102, "Manhattan" , ifelse(substr(dfaTrainDup$zipcode, 1, 3)  ==112  ,"Brooklyn",
ifelse(substr(dfaTrainDup$zipcode, 1, 3)  ==111 | substr(dfaTrainDup$zipcode, 1, 3)  ==113  | substr(dfaTrainDup$zipcode, 1, 3)  ==114 | substr(dfaTrainDup$zipcode, 1, 3)  ==110 | substr(dfaTrainDup$zipcode, 1, 3)  ==116 ,"Queens",
ifelse(substr(dfaTrainDup$zipcode, 1, 3)  ==103 ,"Staten Island",
ifelse(substr(dfaTrainDup$zipcode, 1, 3)  ==104  ,"Bronx","NA"))))))
Warning messages:
1: Unknown or uninitialised column: 'borough'. 
2: Unknown or uninitialised column: 'borough'. 
3: Unknown or uninitialised column: 'borough'. 
4: Unknown or uninitialised column: 'borough'. 
5: Unknown or uninitialised column: 'borough'. 
dfTrainNoZip <-dfaTrainDup %>% filter(is.na(zipcode))




dfTrainNoZip$borough <- dfaTrainDup$borough[match(dfTrainNoZip$neighbourhood, dfaTrainDup$neighbourhood)]


final <- merge(dfaTrainDup, dfTrainNoZip[ , c("id", "borough")], by = "id", all = TRUE)
final$borough <- final$borough.x %?% final$borough.y



final <- final %>% select(-c("borough.x","borough.y"))
dfaTrainDup <- final %>% filter(borough!="NA")

dfaTrainDup <- dfaTrainDup %>% mutate(total_amenities = ifelse(nchar(amenities)>2, str_count(amenities, ',')+1, 0))

Map for Manhattan Borough:

dfM <- dfaTrainDup %>% filter(borough=='Manhattan') 
library("leaflet")
#Creating Listings across NYC
leaflet(dplyr::bind_rows(dfM,dfzipS)) %>%
 addTiles() %>%
 addCircleMarkers(data=dfM, color = '#9D7',
    opacity = 1,~dfM$longitude, ~dfM$latitude,labelOptions = labelOptions(noHide = F),clusterOptions = markerClusterOptions(),popup = paste0( "<br> <b> Price: </b>", dfM$price, "<br/><b> Room Type: </b>", dfM$room_type, "<br/><b> Property Type: </b>", dfM$property_type,"<br> <b> Amenities: </b>", dfM$total_amenities,"<br> <b> Review Score: </b>", dfM$review_scores_value,"<br> <b> Amenities: </b>", dfM$total_amenities,"<br> <b> Booking Rate: </b>", dfM$high_booking_rate
           )) %>% 
  addCircleMarkers(data=dfzipS,~longitude, ~latitude,color = '#FA5',labelOptions = labelOptions(noHide = F),clusterOptions = markerClusterOptions(),opacity = 1,popup = paste0( "<br> <b> Station Name: </b>", dfzipS$Stationname)) %>% 
  setView(-74.00, 40.71, zoom = 12)

 #%>%
  #addProviderTiles("CartoDB.Positron")

Map for Brooklyn Borough:

dfB <- dfaTrainDup %>% filter(borough=='Brooklyn')
library("leaflet")
#Creating Listings across NYC
leaflet(dplyr::bind_rows(dfB,dfzipS)) %>%
 addTiles() %>%
 addCircleMarkers(data=dfB, color = '#9D7',
    opacity = 1,~longitude, ~latitude,labelOptions = labelOptions(noHide = F),clusterOptions = markerClusterOptions(),popup = paste0( "<br> <b> Price: </b>", dfB$price, "<br/><b> Room Type: </b>", dfB$room_type, "<br/><b> Property Type: </b>", dfB$property_type,"<br> <b> Amenities: </b>", dfB$total_amenities,"<br> <b> Review Score: </b>", dfB$review_scores_value,"<br> <b> Amenities: </b>", dfB$total_amenities,"<br> <b> Booking Rate: </b>", dfB$high_booking_rate
           )) %>% 
  addCircleMarkers(data=dfzipS,color = '#FA5',opacity = 1,popup = paste0( "<br> <b> Station Name: </b>", dfzipS$Stationname)) %>% 
 setView(-74.00, 40.71, zoom = 12)%>%
  addProviderTiles("CartoDB.Positron")
Assuming "longitude" and "latitude" are longitude and latitude, respectively

Map for Queens Borough:

dfQ <- dfaTrainDup %>% filter(borough=='Queens')
library("leaflet")
#Creating Listings across NYC
leaflet(dplyr::bind_rows(dfQ,dfzipS)) %>%
 addTiles() %>%
 addCircleMarkers(data=dfQ, color = '#9D7',
    opacity = 1,~longitude, ~latitude,labelOptions = labelOptions(noHide = F),clusterOptions = markerClusterOptions(),popup = paste0( "<br> <b> Price: </b>", dfQ$price, "<br/><b> Room Type: </b>", dfQ$room_type, "<br/><b> Property Type: </b>", dfQ$property_type,"<br> <b> Amenities: </b>", dfQ$total_amenities,"<br> <b> Review Score: </b>", dfQ$review_scores_value,"<br> <b> Amenities: </b>", dfQ$total_amenities,"<br> <b> Booking Rate: </b>", dfQ$high_booking_rate
           )) %>% 
  addCircleMarkers(data=dfzipS,color = '#FA5',opacity = 1,popup = paste0( "<br> <b> Station Name: </b>", dfzipS$Stationname)) %>% 
  setView(-74.00, 40.71, zoom = 12)
Assuming "longitude" and "latitude" are longitude and latitude, respectively

 #%>%
  #addProviderTiles("CartoDB.Positron")

Map for Bronx Borough:

dfBr <- dfaTrainDup %>% filter(borough=='Bronx')
library("leaflet")
#Creating Listings across NYC
leaflet(dplyr::bind_rows(dfBr,dfzipS)) %>%
 addTiles() %>%
 addCircleMarkers(data=dfBr, color = '#9D7',
    opacity = 1,~longitude, ~latitude,labelOptions = labelOptions(noHide = F),clusterOptions = markerClusterOptions(),popup = paste0( "<br> <b> Price: </b>", dfBr$price, "<br/><b> Room Type: </b>", dfBr$room_type, "<br/><b> Property Type: </b>", dfBr$property_type,"<br> <b> Amenities: </b>", dfBr$total_amenities,"<br> <b> Review Score: </b>", dfBr$review_scores_value,"<br> <b> Amenities: </b>", dfBr$total_amenities,"<br> <b> Booking Rate: </b>", dfBr$high_booking_rate
           )) %>% 
  addCircleMarkers(data=dfzipS,color = '#FA5',opacity = 1,popup = paste0( "<br> <b> Station Name: </b>", dfzipS$Stationname)) %>% 
  setView(-74.00, 40.71, zoom = 12)
Assuming "longitude" and "latitude" are longitude and latitude, respectively

Map for Staten Island Borough:

dfS <- dfaTrainDup %>% filter(borough=='Staten Island')
library("leaflet")
#Creating Listings across NYC
leaflet(dplyr::bind_rows(dfS,dfzipS)) %>%
 addTiles() %>%
 addCircleMarkers(data=dfS, color = '#9D7',
    opacity = 1,~longitude, ~latitude,labelOptions = labelOptions(noHide = F),clusterOptions = markerClusterOptions(),popup = paste0( "<br> <b> Price: </b>", dfS$price, "<br/><b> Room Type: </b>", dfS$room_type, "<br/><b> Property Type: </b>", dfS$property_type,"<br> <b> Amenities: </b>", dfS$total_amenities,"<br> <b> Review Score: </b>", dfS$review_scores_value,"<br> <b> Amenities: </b>", dfS$total_amenities,"<br> <b> Booking Rate: </b>", dfS$high_booking_rate
           )) %>% 
  addCircleMarkers(data=dfzipS,color = '#FA5',opacity = 1,popup = paste0( "<br> <b> Station Name: </b>", dfzipS$Stationname)) %>% 
  setView(-74.00, 40.71, zoom = 12)
Assuming "longitude" and "latitude" are longitude and latitude, respectively

2. Data Exploration and Visualization

a) Distribution of high and low booking rates across boroughs
dfaTrain %>% 
  group_by(borough) %>% 
  summarize(HighBookingYes = length(borough[as.factor(high_booking_rate) == 1]),
            HighBookingNo = length(borough[as.factor(high_booking_rate) == 0])) %>%
  mutate(HighBookingYesPct = HighBookingYes*100/(HighBookingYes+HighBookingNo),
         HighBookingNoPct = HighBookingNo*100/(HighBookingYes+HighBookingNo)) %>%
  arrange(desc(HighBookingYesPct))

Manhattan and Brooklyn have the most number of properties. However, this doesn’t necessarily translate into a greater percentage of high booking rates. Staten Island with the least number of properties, has a relatively greater percentage of high booking rates.

plotBoroughBooking <- ggplot(data = dfaTrain, aes(x=borough, fill=as.factor(high_booking_rate))) +
            geom_histogram(color='black',stat='count') +xlab('Borough') + ylab('Number of Properties') +ggtitle("Distribution of booking rates broken down by boroughs")
Ignoring unknown parameters: binwidth, bins, pad
plotBoroughBooking

The properties with high booking rates are less than the ones with low booking rates. This might be due to lesser number of properties having certain desired amenities as compared to the properties with all required amenities.

b) Distribution of prices across boroughs
plotPriceBorough <- ggplot(data = dfaTrain, aes(x=as.factor(borough), y=price)) +
            geom_boxplot() + xlab('Borough') +ylab('Price')
plotPriceBorough

plotPriceBorough2 <- ggplot(data = dfaTrain, aes(x=as.factor(borough), y=price)) +
            geom_boxplot(outlier.shape = NA) + xlab('Borough') +ylab('Price') + ylim(0,500)
plotPriceBorough2

Prices seem to be higher and have a wider distribution in Manhattan and Brooklyn. This might also be contributing to a relatively lower percentage of high booking rates in these boroughs.

Is there multicollinearity between price and location?

car::vif(lm(high_booking_rate ~ borough+price,data=dfaTrain))
            GVIF Df GVIF^(1/(2*Df))
borough 1.017509  4        1.002172
price   1.017509  1        1.008717

Price moves independently of location. There doesn’t seem to be direct correlation between location and price.

c) Impact of review scores on booking rate
#Review Scores
summary(lm(high_booking_rate~review_scores_accuracy+review_scores_checkin+review_scores_cleanliness+review_scores_communication+review_scores_location+review_scores_rating+review_scores_value,data=dfaTrain))

Call:
lm(formula = high_booking_rate ~ review_scores_accuracy + review_scores_checkin + 
    review_scores_cleanliness + review_scores_communication + 
    review_scores_location + review_scores_rating + review_scores_value, 
    data = dfaTrain)

Residuals:
    Min      1Q  Median      3Q     Max 
-0.7114 -0.2741 -0.2504  0.6655  0.9821 

Coefficients:
                              Estimate Std. Error t value Pr(>|t|)    
(Intercept)                 -0.3109355  0.0448551  -6.932 4.26e-12 ***
review_scores_accuracy       0.0421256  0.0054898   7.673 1.74e-14 ***
review_scores_checkin        0.0486050  0.0054766   8.875  < 2e-16 ***
review_scores_cleanliness    0.0445799  0.0038287  11.644  < 2e-16 ***
review_scores_communication  0.0239246  0.0057342   4.172 3.03e-05 ***
review_scores_location      -0.0243961  0.0043704  -5.582 2.40e-08 ***
review_scores_rating        -0.0075461  0.0006699 -11.264  < 2e-16 ***
review_scores_value         -0.0010512  0.0051130  -0.206    0.837    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.4349 on 23617 degrees of freedom
  (6550 observations deleted due to missingness)
Multiple R-squared:  0.02145,   Adjusted R-squared:  0.02116 
F-statistic: 73.94 on 7 and 23617 DF,  p-value: < 2.2e-16

Except for review_score_value, all other review scores seem to be statistically significant. Is there multicollinearity between the review scores ?

car:: vif(lm(high_booking_rate~review_scores_accuracy+review_scores_checkin+review_scores_cleanliness+review_scores_communication+review_scores_location+review_scores_rating+review_scores_value,data=dfaTrain))
     review_scores_accuracy       review_scores_checkin   review_scores_cleanliness review_scores_communication 
                   2.770427                    2.057186                    2.157580                    2.376300 
     review_scores_location        review_scores_rating         review_scores_value 
                   1.396017                    4.310059                    2.843359 

Yes, there seem to be multicollinearity between the review scores. Possibly, review score rating is the overall rating and hence could be the reason for the multicollinearity.

car:: vif(lm(high_booking_rate~review_scores_accuracy+review_scores_checkin+review_scores_cleanliness+review_scores_communication+review_scores_location+review_scores_value,data=dfaTrain))
     review_scores_accuracy       review_scores_checkin   review_scores_cleanliness review_scores_communication 
                   2.575793                    2.049272                    1.834114                    2.265716 
     review_scores_location         review_scores_value 
                   1.381711                    2.468226 
car:: vif(lm(high_booking_rate~review_scores_checkin+review_scores_cleanliness+review_scores_communication+review_scores_location+review_scores_value,data=dfaTrain))
      review_scores_checkin   review_scores_cleanliness review_scores_communication      review_scores_location 
                   1.991415                    1.714060                    2.183338                    1.370716 
        review_scores_value 
                   2.146033 

Also, review_scores_accuracy captures information about how accurately the space was represented by the listing. A part of this information could also be captured by the other review_scores. Hence, we could remove review_score_accuracy as well.

summary(lm(high_booking_rate~review_scores_checkin+review_scores_cleanliness+review_scores_communication+review_scores_location+review_scores_value,data=dfaTrain))

Call:
lm(formula = high_booking_rate ~ review_scores_checkin + review_scores_cleanliness + 
    review_scores_communication + review_scores_location + review_scores_value, 
    data = dfaTrain)

Residuals:
    Min      1Q  Median      3Q     Max 
-0.5536 -0.2816 -0.2495  0.6831  0.9069 

Coefficients:
                             Estimate Std. Error t value Pr(>|t|)    
(Intercept)                 -0.251603   0.044659  -5.634 1.78e-08 ***
review_scores_checkin        0.047531   0.005392   8.815  < 2e-16 ***
review_scores_cleanliness    0.032111   0.003420   9.390  < 2e-16 ***
review_scores_communication  0.014373   0.005501   2.613  0.00899 ** 
review_scores_location      -0.026757   0.004344  -6.160 7.38e-10 ***
review_scores_value         -0.013942   0.004450  -3.133  0.00173 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.4362 on 23622 degrees of freedom
  (6547 observations deleted due to missingness)
Multiple R-squared:  0.01522,   Adjusted R-squared:  0.01501 
F-statistic: 73.02 on 5 and 23622 DF,  p-value: < 2.2e-16
d) Distribution of property types across boroughs

propertydf <-  dfaTrainDup %>% group_by(borough, property_type) %>% summarize(Freq = n())
propertydf <- propertydf %>% filter(property_type %in% c("Apartment","House","Condominium","Townhouse", "Loft","Guest suite"))
totalproperty<-  dfaTrainDup %>% filter(property_type %in% c("Apartment","House","Condominium","Townhouse", "Loft","Guest suite"))%>% group_by(borough) %>% summarize(sum = n())

propertyratio <- merge(propertydf, totalproperty, by="borough")
propertyratio <- propertyratio %>% mutate(ratio = Freq/sum)
ggplot(propertyratio, aes(x=borough, y=ratio, fill = property_type)) +
  geom_bar(position = "dodge",stat="identity") + xlab("Borough") + ylab("Count")+
  scale_fill_discrete(name = "Property Type") + 
  scale_y_continuous(labels = scales::percent) +
  ggtitle("Property Types in NYC",
          subtitle = "Map showing Percentage Count of Property Type by Borough ") +
          theme(plot.title = element_text(face = "bold")) +
          theme(plot.subtitle = element_text(face = "bold", color = "grey35")) +
          theme(plot.caption = element_text(color = "grey68"))+scale_color_gradient(low="#d3cbcb", high="#852eaa")+
          scale_fill_manual("Property Type", values=c("#e06f69","#357b8a", "#7db5b8", "#59c6f3", "#f6c458","#00FF00")) +
          xlab("Neighborhood") + ylab("Percentage")

Considering the most preferred property types in all the boroughs plotted above,we can see that Apartment is the most preferred Property type in Bronx, Brooklyn, Manhattan and Queens.This could be mainly due to the people visiting these boroughs for sight seeing or business trips. On the other hand, Houses are most preferred in Staten Island as they are less expensive than the houses in other boroughs and people visit it mostly for leisure.

e) Room Type Analysis

Distrbution of properties in NYC based on room type along with the count of the high booking rate properties and it’s percentage

dfr1 <- dfaTrain %>% group_by(room_type) %>% summarise(count_highbooking=sum(high_booking_rate),total=length(high_booking_rate))%>%
  mutate(Pct = count_highbooking*100/total) %>% arrange(desc(Pct))
dfr1

Distribution of Properties based on Room Type

plot1 <- ggplot(data = dfr1, aes(x=room_type, y=total,fill=room_type)) +
            geom_bar(stat = "identity", width=0.5,colour="black")+labs(title = "Distribution of Properties based on Room Type",x="Room Type",y="No of Properties")
plot1

Percentage of Properties with high booking rate based on Room Type

plot2 <- ggplot(data = dfr1, aes(x=room_type, y=Pct,fill=room_type)) +
            geom_bar(stat = "identity", width=0.5,colour="black")+labs(title = "Percentage of Properties with high booking rate based on Room Type",x="Room Type",y="Percentage of Properties")
plot2

Distribution of properties with high booking rate in each borough

dfr4 <- dfaTrain %>% filter(high_booking_rate==1) %>% group_by(borough,room_type)%>% 
  summarise(total_high_booking_rate=length(high_booking_rate))

plot3 <- ggplot(data = dfr4, aes(x=borough, y=total_high_booking_rate,fill=room_type)) +
            geom_bar(stat = "identity",position="stack",colour="black")+labs(title="Distribution of properties with high booking rate in each borough",x="Borough",y="Count of high booking rate properties")
plot3

From the above visualisations, we observe that Entire Home/Apartment and Private rooms are the most popular room types offered in NYC and also the most successful ones in terms of high booking rate.

If we try going neighbourhood wise,

dfaTrain %>% group_by(room_type,neighbourhood) %>% summarise(count_highbooking=sum(high_booking_rate),total=length(high_booking_rate))%>%
  mutate(Pct = count_highbooking*100/total) %>% arrange(desc(Pct)) %>% filter(total>10)

No such clear inference if we go neighbourhood wise.

Distribution of count of high booking properties in each borough and the total properties based on different room types available (Room types having greater than 10 observations)

dfr2 <- dfaTrain %>% group_by(room_type,borough) %>% summarise(count_highbooking=sum(high_booking_rate),total_properties=length(high_booking_rate))%>%
  mutate(Percentage_highbooking = count_highbooking*100/total_properties) %>% arrange(desc(Percentage_highbooking)) %>% filter(total_properties>10)
dfr2
NA

You can see that Entire home/apt and Private room standout as the most successful ones here when we check every borough

Different roomtypes available in each borough along with their success rate in an ascending order

 dfr3 <- dfaTrain %>% group_by(borough,room_type
) %>% summarise(count_highbooking=sum(high_booking_rate),total_properties=length(high_booking_rate))%>%
  mutate(Percentage_highbooking = count_highbooking*100/total_properties)%>% filter(count_highbooking>5) %>% arrange(Percentage_highbooking)
dfr3
NA

Shared room in Queens or Broolyn is a bad idea but shared in Manhattan may work because of how expensive the area is.Hotel Rooms in Manhattan would not be a good idea.Entire home/apt and Private rooms seem to be having the best Percentage output but all still in the range of 15%-35%

Most popular room types per borough rank wise and visualisation of the successful listings broken down by Room types in each borough

 dfr5 <- dfaTrain %>% group_by(borough,room_type
) %>% summarise(total=length(high_booking_rate)) %>% arrange(borough, desc(total)) %>% mutate(rank=rank(-total))
dfr5

Visualisation of the successful listings broken down by room type in each borough


plot4 <- ggplot(data = dfr5, aes(x=borough, y=total,fill=room_type)) +
            geom_bar(stat = "identity",position="dodge",color='black')+labs(x="Borough",y="Count of successful listings",title="Visualisation of the successful listings broken down by room type in each borough")
plot4

Entire home/apt and Private rooms seem to be having the highest count of successful listings and are also the most popular ones in most of the boroughs.This could also tell us that because they do good booking rate wise they are the most popular or vice versa. Nevertheless, Entire home/apt and Private room seem to be the least risk taking option in NYC.

Some guests are social butterflies and use Airbnb to meet new people on their travels. Other guests prefer to be private and keep to themselves.Choosing an Airbnb entire home means you get the whole place to yourself. There is no sharing with hosts or other guests, it’s just you and your party. The Airbnb hosts do not stay with you when you reserve an entire home.

Why a traveller would prefer an entire home/apartment?

1. No hosts watching your every move.
2. There’s no interaction.
3. An entire place is great when you just want somewhere to relax. If you’ve been busy all day, sometimes the last thing you want to do is come back and have to be social, talking to strangers.

Why a traveller would prefer a Private room?

1. They are super cheap.
2. For people who enjoy interacting with others while also having some sort of privacy.
3. Your host knows the area and has great advice of things to see.

So depending on which market the investor wants to target he can opt for either of the above options and create a great listing.

From the above analysis we infer that the investor should either go with leasing the property as an Entire Home/Apartment or a Private room in order to get a better chance of getting a higher booking rate

f) Super Host

Airbnb awards the title of “Superhost” to a small fraction of its dependable hosts. This program is an incentive program that is a win-win for both the host, Airbnb, and their customers. The superhost gains more business in the form of higher bookings, the customer receives improved service and Airbnb profits with happy satisfied customers.

Airbnb’s site requires a host to satisfy certain requirements to be considered as a super host. We consider two parameters from the dataset and measure the performance: “Response rate” and “Ratings” which range from 0 to 100.

The scatter plot gives a few interesting insights. While most super-hosts are in the region of high-rating:high-response-rate region, we can also see a few hosts with response rates less than 75% which violates the 90%+ critera set by Airbnb although there are a very small fraction of the hosts. In regard to Ratings, almost all hosts are rated 75% and above with a very few below 75%. Most Airbnb hosts need to lie in the region of high-rating:high-response region to be considered a super host, but only a small fraction can be super hosts if the conditions are not satisfied.

Host Response Time

Visualisation of the successful listings broken down by the host response time

dfaTrain$host_response_time<- dfaTrain$host_response_time%>% replace_na("N/A")
plot5 <- ggplot(data = dfaTrain %>% filter(high_booking_rate==1), aes(x=host_response_time,fill=host_response_time)) +
            geom_histogram(stat = "count",color='black')+labs(x="Response Time",y="Count of listings with high booking rate ",title="Visualisation of the successful listings broken down by the host response time")
Ignoring unknown parameters: binwidth, bins, pad
plot5

Listings having hosts whose response rate is quick, have a better booking rate. This could be another factor that should be considered after the investor has purchased the property.

Airbnb awards the title of “Superhost” to a small fraction of its dependable hosts. This program is an incentive program that is a win-win for both the host, Airbnb, and their customers. The superhost gains more business in the form of higher bookings, the customer receives improved service and Airbnb profits with happy satisfied customers.

Airbnb’s site requires a host to satisfy certain requirements to be considered as a super host.We consider two parameters from the dataset and measure the performance: “Response rate” and “Ratings” which range from 0 to 100.

The scatter plot gives a few interesting insights. While most super-hosts are in the region of high-rating:high-response-rate region, we can also see a few hosts with response rates less than 75% which violates the 90%+ critera set by Airbnb although there are a very small fraction of the hosts. In regard to Ratings, almost all hosts are rated 75% and above with a very few below 75%. Most Airbnb hosts need to lie in the region of high-rating:high-response region to be considered a super host, but only a small fraction can be super hosts if the conditions are not satisfied.

Property Types in Each Borough:

In the above plot which represents the percentage of property types for each borough, we observe except for staten island, apartments make up for the highest number of observation which is more than 50%. This could be due to the fact that most of the New yorkers (approx 65%) own properties in buldings with more than 10 units i.e., apartments (NYU Furman Center 2017). The same report from NYU Furman center also explains the phenomenon in staten island where there are less number of apartments and more number of houses. This is due to the fact that staten island has approximately 60% of the properties as houses and just over 15% apartments.

Choroplethr Map for Location Review Rating:

library(devtools)
Loading required package: usethis

Attaching package: ‘devtools’

The following object is masked from ‘package:recipes’:

    check
#install_github('arilamstein/choroplethrZip@v1.5.0')
# Accessing countys file to get region of each property
dfzip <- read_csv("NYCountyZip.csv")
Parsed with column specification:
cols(
  countyname = col_character(),
  region = col_double(),
  zipcode = col_double()
)
dfzip$zipcode <- as.character(dfzip$zipcode)
skim(dfzip)
── Data Summary ────────────────────────
                           Values
Name                       dfzip 
Number of rows             2543  
Number of columns          3     
_______________________          
Column type frequency:           
  character                2     
  numeric                  1     
________________________         
Group variables            None  

── Variable type: character ────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique whitespace
1 countyname            0             1     4    12     0       62          0
2 zipcode               0             1     3     5     0     2169          0

── Variable type: numeric ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate   mean    sd    p0   p25   p50   p75  p100 hist 
1 region                0             1 36062.  35.0 36001 36031 36061 36091 36123 ▆▅▇▅▆

Merging the New York dataset with county file:

dfaTrainDup2 <- merge(dfaTrainDup,dfzip,by.x="zipcode",by.y = "zipcode",all.x= TRUE) 
dfaTrainDup <- dfaTrainDup2

Checking the columns required for plotting a choroplethr map:

data(county.regions, 
     package = "choroplethrMaps")
region <- county.regions %>%
  filter(state.name == "new york")

region

Joining the region data for choroplethr map with our data:

plotdata <- inner_join(dfaTrainDup, 
                       region, 
                       by=c("region" = "region"))

Plotting the choroplethr map:

The map above represents the location reviews based on counties. We observe that Clinton county has the highest location review followed by Albany, and then Delaware.

Finding nearest subway Station and number of stations near to the property:

dfzipS$Station = paste(dfzipS$Line, dfzipS$Stationname, sep="-")
NYCSub_Stations<-aggregate(dfzipS[, 3:4], list(dfzipS$Station), mean)
colnames(NYCSub_Stations)[1] <- "Station"
dfaTrainDup$lat_dec<-format(round(dfaTrainDup$latitude, 3), nsmall = 3)

dfaTrainDup$lon_dec<-format(round(dfaTrainDup$longitude, 3), nsmall = 3)

dfaTrainDup$lat_lon = paste(dfaTrainDup$lat_dec, dfaTrainDup$lon_dec, sep="_")

NYCBor_Short<-dfaTrainDup%>%select(lat_lon,lat_dec, lon_dec)

NYCBor_short_dup<-NYCBor_Short[!duplicated(NYCBor_Short), ]

NYCBor_short_dup$shortest_metro=NA

NYCBor_short_dup$number_less_than_1=NA

Code for finding the nearest metro station and number of metro stations within a mile:

# NYCBor_short_dup$lat_dec<-as.numeric(NYCBor_short_dup$lat_dec)
# NYCBor_short_dup$lon_dec<-as.numeric(NYCBor_short_dup$lon_dec)
# 
# 
# for (i in 1:nrow(NYCBor_short_dup)){
#   
#   #print(i)
#   
#   shortest_dist = 1000
#   
#   num_less_1 = 0
#   
#   for (j in 1:nrow(NYCSub_Stations)){
#     
#    distance = sqrt(((NYCBor_short_dup[i,2]-NYCSub_Stations[j,2])*(NYCBor_short_dup[i,2]-NYCSub_Stations[j,2])) +    ((NYCBor_short_dup[i,3]-NYCSub_Stations[j,3])*(NYCBor_short_dup[i,3]-NYCSub_Stations[j,3]))) * 69
#    
#   if (distance<=1){
#     num_less_1 = num_less_1 + 1
#   }
#    
#   if(distance<shortest_dist){
#     shortest_dist=distance
#   }
#    else{
#      shortest_dist=shortest_dist
#    }
#   
#     
#   }
#   
#   NYCBor_short_dup[i,4] = shortest_dist
#   
#   NYCBor_short_dup[i,5] = num_less_1
# }
# dfaTrainDup <- merge(dfaTrainDup,NYCBor_short_dup,by.x="lat_lon",by.y = "lat_lon",all.x= TRUE) 
dfaTrainDup <- read_csv("Megha_NYC_Data.csv")
Parsed with column specification:
cols(
  .default = col_double(),
  lat_lon = col_character(),
  access = col_character(),
  amenities = col_character(),
  bed_type = col_character(),
  cancellation_policy = col_character(),
  cleaning_fee = col_character(),
  extra_people = col_character(),
  host_about = col_character(),
  host_identity_verified = col_logical(),
  host_is_superhost = col_logical(),
  host_neighbourhood = col_character(),
  host_response_rate = col_character(),
  host_response_time = col_character(),
  host_since = col_date(format = ""),
  host_verifications = col_character(),
  house_rules = col_character(),
  instant_bookable = col_logical(),
  interaction = col_character(),
  is_location_exact = col_logical(),
  market = col_character()
  # ... with 14 more columns
)
See spec(...) for full column specifications.
3 parsing failures.
  row     col               expected  actual                 file
 2911 zipcode no trailing characters -3220   'Megha_NYC_Data.csv'
14735 zipcode no trailing characters 
11249 'Megha_NYC_Data.csv'
23630 zipcode no trailing characters -3233   'Megha_NYC_Data.csv'
dfaTrainDup %>% select(id,borough,shortest_metro,number_less_than_1,high_booking_rate) %>% arrange(desc(number_less_than_1))

Bar chart of population of high booking rate for properties with metro stations within a mile:

dfaTrainDupPlotMetro <- dfaTrainDup %>% group_by(number_less_than_1)%>%summarize(Freq = mean(high_booking_rate)) %>% 
  ggplot( aes(x = number_less_than_1, y = Freq))+
  geom_bar( stat = "identity") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))+
  ggtitle("Proportion of High Booking Rate by Number of metro stations within a mile") +
          theme(plot.title = element_text(face = "bold")) +
          theme(plot.subtitle = element_text(face = "bold", color = "grey35")) +
          xlab("Count of metro stations within a mile for properties") + ylab("High Booking rate Proportion")
dfaTrainDupPlotMetro

df_total = data.frame()
for (val in 0:max(dfaTrainDup$number_less_than_1)){
  x<-dfaTrainDup %>% filter(number_less_than_1<=val) %>% summarize(meanforlesser=mean(high_booking_rate))
  y<- dfaTrainDup %>% filter(number_less_than_1>val) %>% summarize(meanforgreater=mean(high_booking_rate))
  #print(c(val,x$mean, y$mean))
   df <- data.frame(val,x,y)
  df_total <- rbind(df_total,df)
}
df_total

This bar graph and the above resultset shows that the booking rate of property decreases as the number of metro stations within a mile increases. This phenomenon s observed until the number of stations is 19. But we also observe that 19 is the inflection point after which the trend reverses i.e., booking rate increases with an increase in number of metro stations within 1 mile. The first phenomenon could be due to people in those regions prefer a lower price of the property than comfort for transiting. The phenomenon revarsal could due to the fact that people might be looking for comfort for transiting than the higher price to be paid for the property. More number of metro stations within a mile may be mostly seen around Manhattan and Brooklyn where people might visit the places for business purposes and have the need to shuttle more often within NYC.

Bar graph for count of metro stations within a mile for properties:

dfaTrainDup$stationsinamile1 <- cut(x=dfaTrainDup$number_less_than_1, breaks=seq(from=-1, to=ceiling(max(dfaTrainDup$number_less_than_1)), by = 5))
dfaTrainDupPlotMetro1 <- dfaTrainDup %>% group_by(stationsinamile1,high_booking_rate)%>%summarize(Freq = n()) %>% 
  ggplot( aes(x = stationsinamile1, y = Freq,fill = as.factor(high_booking_rate)))+
  geom_bar( stat = "identity") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))+
    ggtitle("Count of Properties by number of metro stations within a mile") +
          theme(plot.title = element_text(face = "bold")) +
          theme(plot.subtitle = element_text(face = "bold", color = "grey35")) +
          xlab("Number of metro stations within a mile") + ylab("Count of properties")
Factor `stationsinamile1` contains implicit NA, consider using `forcats::fct_explicit_na`
dfaTrainDupPlotMetro1

dfaTrain <- merge(dfaTrain,dfaTrainDup %>% select(id, recode2), by.x="id", by.y = "id",all.x= TRUE) 
df <- dfaTrain %>% mutate(stations_in_a_mile = as.factor(recode2))

This bar graph indicates that as the number of properties dereases with an increase in number of metro stations within a mile of the property. Subsequently, we observe a decrease in high booking rate.

RESULTS AND FINDINGS

The insights about the statistically significant and non-significant variables helped us to decide what variables to include in the logistic regression model for the NYC data.

set.seed(123)
dfaTrain <- sample_frac(df, 0.75)
dfaTest <- dplyr::setdiff(df, dfaTrain)
cols_to_select <- c(
"high_booking_rate",
"\"24-hour check-in\"",
"\"Air conditioning\"",
"\"Bed linens\"",
"\"Cable TV\"",
"\"Cleaning before checkout\"",
"\"Coffee maker\"",
"\"Dishes and silverware\"",
"\"Family/kid friendly\"",
"\"Free street parking\"",
"\"Hair dryer\"",
"\"Host greets you\"",
"\"Hot water\"",
"\"Laptop friendly workspace\"",
"\"Luggage dropoff allowed\"",
"\"No stairs or steps to enter\"",
"\"Paid parking off premises\"",
"\"Pets allowed\"",
"\"Pets live on this property\"",
"\"Safety card\"",
"\"Self check-in\"",
"\"Smoke detector\"",
"Dishwasher",
"Doorman",
"Elevator",
"Gym",
"Heating",
"Internet",
"Iron",
"Kitchen",
"Microwave",
"Oven",
"TV",
"Washer",
"property_type",
"host_is_superhost",
"room_type",
"review_scores_checkin",
"review_scores_location",
"review_scores_value",
"borough",
"price",
"access",
"host_response_time",
"cleaning_fee",
"host_since",
"house_rules",
"minimum_nights",
"instant_bookable",
"stations_in_a_mile"
)

dfaTrain <- dfaTrain %>% 
  select(cols_to_select)
dfaTrain <- dfaTrain %>% 
  mutate(high_booking_rate = as.factor(high_booking_rate))
glmModel <- train(high_booking_rate ~  .,
                  family = 'binomial',
                  method = "glm",
                  data = dfaTrain %>% drop_na())
summary(glmModel)

Call:
NULL

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.7868  -0.5913  -0.2892   0.3974   5.4598  

Coefficients:
                                         Estimate Std. Error z value Pr(>|z|)    
(Intercept)                             6.787e-01  7.491e-01   0.906 0.364923    
`\\`"24-hour check-in"\\`1`             5.263e-01  7.928e-02   6.638 3.18e-11 ***
`\\`"Air conditioning"\\`1`             2.781e-01  7.398e-02   3.759 0.000171 ***
`\\`"Bed linens"\\`1`                  -1.414e-01  5.498e-02  -2.571 0.010135 *  
`\\`"Cable TV"\\`1`                     3.604e-01  5.568e-02   6.474 9.55e-11 ***
`\\`"Cleaning before checkout"\\`1`    -3.096e-01  1.088e-01  -2.846 0.004433 ** 
`\\`"Coffee maker"\\`1`                 1.467e-01  6.149e-02   2.386 0.017030 *  
`\\`"Dishes and silverware"\\`1`        3.292e-01  7.449e-02   4.420 9.88e-06 ***
`\\`"Family/kid friendly"\\`1`          8.732e-01  5.344e-02  16.338  < 2e-16 ***
`\\`"Free street parking"\\`1`          2.557e-01  5.535e-02   4.619 3.85e-06 ***
`\\`"Hair dryer"\\`1`                   5.231e-01  6.292e-02   8.313  < 2e-16 ***
`\\`"Host greets you"\\`1`              3.344e-01  6.142e-02   5.445 5.19e-08 ***
`\\`"Hot water"\\`1`                    6.741e-02  6.397e-02   1.054 0.292011    
`\\`"Laptop friendly workspace"\\`1`   -2.167e-01  5.284e-02  -4.100 4.13e-05 ***
`\\`"Luggage dropoff allowed"\\`1`      9.369e-02  5.610e-02   1.670 0.094918 .  
`\\`"No stairs or steps to enter"\\`1`  2.505e-01  7.070e-02   3.543 0.000395 ***
`\\`"Paid parking off premises"\\`1`    1.680e-01  6.752e-02   2.487 0.012871 *  
`\\`"Pets allowed"\\`1`                -1.912e-01  7.335e-02  -2.607 0.009144 ** 
`\\`"Pets live on this property"\\`1`   3.878e-01  8.563e-02   4.529 5.91e-06 ***
`\\`"Safety card"\\`1`                  1.996e-01  7.515e-02   2.657 0.007894 ** 
`\\`"Self check-in"\\`1`                3.866e-01  5.505e-02   7.023 2.17e-12 ***
`\\`"Smoke detector"\\`1`              -2.108e-01  7.801e-02  -2.702 0.006899 ** 
Dishwasher1                            -1.187e-01  6.645e-02  -1.786 0.074042 .  
Doorman1                               -3.500e-01  1.462e-01  -2.394 0.016657 *  
Elevator1                              -2.014e-01  6.567e-02  -3.067 0.002161 ** 
Gym1                                   -8.207e-01  1.098e-01  -7.477 7.61e-14 ***
Heating1                                5.322e-01  1.400e-01   3.802 0.000144 ***
Internet1                               6.854e-01  6.036e-02  11.355  < 2e-16 ***
Iron1                                   2.044e-01  5.861e-02   3.487 0.000489 ***
Kitchen1                               -3.684e-01  7.943e-02  -4.638 3.51e-06 ***
Microwave1                              1.602e-01  6.312e-02   2.538 0.011161 *  
Oven1                                  -1.228e-01  6.893e-02  -1.782 0.074757 .  
TV1                                    -2.291e-01  5.332e-02  -4.297 1.73e-05 ***
Washer1                                -3.717e-01  5.271e-02  -7.051 1.77e-12 ***
property_typeApartment                  8.946e-02  7.845e-02   1.140 0.254122    
property_typeHouse                      1.233e-01  1.028e-01   1.199 0.230533    
property_typeTownhouse                  1.114e-01  1.258e-01   0.885 0.376195    
host_is_superhostTRUE                   9.422e-01  4.996e-02  18.859  < 2e-16 ***
`room_typeHotel room`                  -4.631e-01  2.798e-01  -1.655 0.097961 .  
`room_typePrivate room`                -6.172e-02  5.750e-02  -1.073 0.283112    
`room_typeShared room`                 -7.639e-01  1.583e-01  -4.825 1.40e-06 ***
review_scores_checkin                   3.116e-01  5.006e-02   6.225 4.83e-10 ***
review_scores_location                 -6.700e-02  3.806e-02  -1.761 0.078318 .  
review_scores_value                    -1.389e-01  3.421e-02  -4.060 4.91e-05 ***
boroughBrooklyn                         1.371e-01  1.312e-01   1.046 0.295729    
boroughManhattan                        5.350e-01  1.350e-01   3.963 7.41e-05 ***
boroughQueens                           1.399e-01  1.360e-01   1.029 0.303648    
`boroughStaten Island`                  9.822e-02  2.439e-01   0.403 0.687143    
price                                  -1.518e-03  2.603e-04  -5.832 5.49e-09 ***
accessTRUE                              5.030e-01  4.965e-02  10.132  < 2e-16 ***
`host_response_timeN/A`                -1.185e+00  2.108e-01  -5.622 1.89e-08 ***
`host_response_timewithin a day`        2.952e-01  2.117e-01   1.395 0.163104    
`host_response_timewithin a few hours`  5.102e-01  2.075e-01   2.459 0.013924 *  
`host_response_timewithin an hour`      7.707e-01  2.044e-01   3.770 0.000163 ***
cleaning_fee                           -4.171e-03  6.048e-04  -6.896 5.35e-12 ***
host_since                             -3.171e-04  2.827e-05 -11.219  < 2e-16 ***
house_rulesTRUE                         4.599e-01  5.185e-02   8.870  < 2e-16 ***
minimum_nights                         -6.714e-02  3.779e-03 -17.768  < 2e-16 ***
instant_bookableTRUE                    2.602e-01  4.858e-02   5.355 8.54e-08 ***
stations_in_a_mile1                     3.815e-01  8.517e-02   4.479 7.50e-06 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 20355  on 17693  degrees of freedom
Residual deviance: 13520  on 17634  degrees of freedom
AIC: 13640

Number of Fisher Scoring iterations: 6
exp(coef(glm(high_booking_rate ~ ., family = "binomial", data = dfaTrain %>% drop_na())))
glm.fit: fitted probabilities numerically 0 or 1 occurred
                         (Intercept)                `"24-hour check-in"`1                `"Air conditioning"`1 
                           1.9713410                            1.6925894                            1.3205960 
                     `"Bed linens"`1                        `"Cable TV"`1        `"Cleaning before checkout"`1 
                           0.8681817                            1.4339735                            0.7337471 
                   `"Coffee maker"`1           `"Dishes and silverware"`1             `"Family/kid friendly"`1 
                           1.1580249                            1.3899111                            2.3944593 
            `"Free street parking"`1                      `"Hair dryer"`1                 `"Host greets you"`1 
                           1.2913341                            1.6872255                            1.3970831 
                      `"Hot water"`1       `"Laptop friendly workspace"`1         `"Luggage dropoff allowed"`1 
                           1.0697340                            0.8052088                            1.0982148 
    `"No stairs or steps to enter"`1       `"Paid parking off premises"`1                    `"Pets allowed"`1 
                           1.2846848                            1.1828803                            0.8259628 
     `"Pets live on this property"`1                     `"Safety card"`1                   `"Self check-in"`1 
                           1.4737835                            1.2209506                            1.4719674 
                 `"Smoke detector"`1                          Dishwasher1                             Doorman1 
                           0.8099667                            0.8880696                            0.7046735 
                           Elevator1                                 Gym1                             Heating1 
                           0.8175679                            0.4401253                            1.7026123 
                           Internet1                                Iron1                             Kitchen1 
                           1.9845323                            1.2267301                            0.6918304 
                          Microwave1                                Oven1                                  TV1 
                           1.1737045                            0.8844091                            0.7952471 
                             Washer1               property_typeApartment                   property_typeHouse 
                           0.6895681                            1.0935872                            1.1312039 
              property_typeTownhouse                host_is_superhostTRUE                  room_typeHotel room 
                           1.1177972                            2.5656131                            0.6293512 
               room_typePrivate room                 room_typeShared room                review_scores_checkin 
                           0.9401467                            0.4658532                            1.3656097 
              review_scores_location                  review_scores_value                      boroughBrooklyn 
                           0.9351941                            0.8703398                            1.1469955 
                    boroughManhattan                        boroughQueens                 boroughStaten Island 
                           1.7074202                            1.1502013                            1.1032031 
                               price                           accessTRUE                host_response_timeN/A 
                           0.9984831                            1.6536926                            0.3056733 
      host_response_timewithin a day host_response_timewithin a few hours     host_response_timewithin an hour 
                           1.3434498                            1.6655940                            2.1612810 
                        cleaning_fee                           host_since                      house_rulesTRUE 
                           0.9958379                            0.9996829                            1.5839345 
                      minimum_nights                 instant_bookableTRUE                  stations_in_a_mile1 
                           0.9350610                            1.2971510                            1.4644168 
skim(dfaTrain)
── Data Summary ────────────────────────
                           Values  
Name                       dfaTrain
Number of rows             22941   
Number of columns          50      
_______________________            
Column type frequency:             
  character                1       
  Date                     1       
  factor                   42      
  numeric                  6       
________________________           
Group variables            None    

── Variable type: character ────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique whitespace
1 borough               0             1     5    13     0        5          0

── Variable type: Date ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median     n_unique
1 host_since          272         0.988 2008-09-07 2019-12-03 2015-06-15     3519

── Variable type: factor ───────────────────────────────────────────────────────────────────────────────────────────────────────────────
   skim_variable                     n_missing complete_rate ordered n_unique top_counts                                
 1 "high_booking_rate"                       0             1 FALSE          2 0: 18251, 1: 4690                         
 2 "\"24-hour check-in\""                    0             1 FALSE          2 0: 20878, 1: 2063                         
 3 "\"Air conditioning\""                    0             1 FALSE          2 1: 19530, 0: 3411                         
 4 "\"Bed linens\""                          0             1 FALSE          2 0: 15814, 1: 7127                         
 5 "\"Cable TV\""                            0             1 FALSE          2 0: 17652, 1: 5289                         
 6 "\"Cleaning before checkout\""            0             1 FALSE          2 0: 22122, 1: 819                          
 7 "\"Coffee maker\""                        0             1 FALSE          2 0: 16058, 1: 6883                         
 8 "\"Dishes and silverware\""               0             1 FALSE          2 0: 14365, 1: 8576                         
 9 "\"Family/kid friendly\""                 0             1 FALSE          2 0: 17306, 1: 5635                         
10 "\"Free street parking\""                 0             1 FALSE          2 0: 15292, 1: 7649                         
11 "\"Hair dryer\""                          0             1 FALSE          2 1: 15193, 0: 7748                         
12 "\"Host greets you\""                     0             1 FALSE          2 0: 19506, 1: 3435                         
13 "\"Hot water\""                           0             1 FALSE          2 1: 12305, 0: 10636                        
14 "\"Laptop friendly workspace\""           0             1 FALSE          2 1: 14619, 0: 8322                         
15 "\"Luggage dropoff allowed\""             0             1 FALSE          2 0: 19088, 1: 3853                         
16 "\"No stairs or steps to enter\""         0             1 FALSE          2 0: 21042, 1: 1899                         
17 "\"Paid parking off premises\""           0             1 FALSE          2 0: 20751, 1: 2190                         
18 "\"Pets allowed\""                        0             1 FALSE          2 0: 20102, 1: 2839                         
19 "\"Pets live on this property\""          0             1 FALSE          2 0: 21609, 1: 1332                         
20 "\"Safety card\""                         0             1 FALSE          2 0: 21135, 1: 1806                         
21 "\"Self check-in\""                       0             1 FALSE          2 0: 17856, 1: 5085                         
22 "\"Smoke detector\""                      0             1 FALSE          2 1: 19979, 0: 2962                         
23 "Dishwasher"                              0             1 FALSE          2 0: 19609, 1: 3332                         
24 "Doorman"                                 0             1 FALSE          2 0: 21970, 1: 971                          
25 "Elevator"                                0             1 FALSE          2 0: 16725, 1: 6216                         
26 "Gym"                                     0             1 FALSE          2 0: 20751, 1: 2190                         
27 "Heating"                                 0             1 FALSE          2 1: 21485, 0: 1456                         
28 "Internet"                                0             1 FALSE          2 0: 16462, 1: 6479                         
29 "Iron"                                    0             1 FALSE          2 1: 14298, 0: 8643                         
30 "Kitchen"                                 0             1 FALSE          2 1: 20814, 0: 2127                         
31 "Microwave"                               0             1 FALSE          2 0: 15247, 1: 7694                         
32 "Oven"                                    0             1 FALSE          2 0: 15490, 1: 7451                         
33 "TV"                                      0             1 FALSE          2 1: 15623, 0: 7318                         
34 "Washer"                                  0             1 FALSE          2 0: 13777, 1: 9164                         
35 "property_type"                           0             1 FALSE          4 Apa: 18023, Oth: 2264, Hou: 1881, Tow: 773
36 "host_is_superhost"                       0             1 FALSE          2 FAL: 18497, TRU: 4444                     
37 "room_type"                               0             1 FALSE          4 Ent: 11732, Pri: 10455, Sha: 570, Hot: 184
38 "access"                                  0             1 FALSE          2 TRU: 12301, FAL: 10640                    
39 "host_response_time"                      0             1 FALSE          5 wit: 9119, N/A: 8059, wit: 3263, wit: 2066
40 "house_rules"                             0             1 FALSE          2 TRU: 14069, FAL: 8872                     
41 "instant_bookable"                        0             1 FALSE          2 FAL: 14290, TRU: 8651                     
42 "stations_in_a_mile"                      0             1 FALSE          2 0: 21224, 1: 1717                         

── Variable type: numeric ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable          n_missing complete_rate   mean      sd    p0   p25   p50   p75  p100 hist 
1 review_scores_checkin       5013         0.781   9.73   0.743     2    10  10      10    10 ▁▁▁▁▇
2 review_scores_location      5014         0.781   9.58   0.759     2     9  10      10    10 ▁▁▁▁▇
3 review_scores_value         5016         0.781   9.39   0.926     2     9  10      10    10 ▁▁▁▁▇
4 price                          0         1     158.   349.        0    69 105     175 10000 ▇▁▁▁▁
5 cleaning_fee                   0         1      66.8   50.6       0    30  67.5    80   600 ▇▁▁▁▁
6 minimum_nights                 0         1       7.27  21.0       1     1   2       5  1000 ▇▁▁▁▁
resultsGLM <-
  glmModel %>%
  predict(dfaTest %>%  drop_na() , type = 'prob') %>% 
  bind_cols(dfaTest %>%  drop_na() , predictedClass = .) %>%
  mutate(predictedClass = as.factor(ifelse(`1` > 0.6 , 1, 0)))

resultsGLM %>%
  xtabs(~predictedClass+high_booking_rate, .) %>%
  confusionMatrix(positive = '1')
Confusion Matrix and Statistics

              high_booking_rate
predictedClass 0 1
             0 5 4
             1 1 2
                                          
               Accuracy : 0.5833          
                 95% CI : (0.2767, 0.8483)
    No Information Rate : 0.5             
    P-Value [Acc > NIR] : 0.3872          
                                          
                  Kappa : 0.1667          
                                          
 Mcnemar's Test P-Value : 0.3711          
                                          
            Sensitivity : 0.3333          
            Specificity : 0.8333          
         Pos Pred Value : 0.6667          
         Neg Pred Value : 0.5556          
             Prevalence : 0.5000          
         Detection Rate : 0.1667          
   Detection Prevalence : 0.2500          
      Balanced Accuracy : 0.5833          
                                          
       'Positive' Class : 1               
                                          

We developed our business case focusing on avoiding the negative consequences of investing in properties that would yield low booking rates. Therefore, we wanted to minimize the False Positive Rate (FPR) . We increased the specificity. The cutoff of 0.6 reduced the number of false positives. We had to make a tradeoff since decreasing false positives increased the false negatives as well. However, for a property based in NY which can be expensive, false positives or incorrectly classifying a property with a low booking rate as one with a high booking rate would have a greater risk associated with it to the investor.

We have 2 part recommendations to achieve high booking rates. The first is about products to be purchased or factors to be considered before buying the property. The second set focuses on maintenance and marketing of the property along with services to be offered to the guests during their stay. While the first part is important, we observe that more emphasis should be laid on services offered after purchasing the property.

  1. Recommendations Before Buying the Property
    1. Invest in a smartbox or keypad to facilitate easy self 24 hour check-in
    2. Buy properties with free parking
    3. Provide access to dishes and silverware
    4. Consider investing in a house over an apartment or a townhouse
    5. Do not pay a premium on a property unless it is in Manhattan
    6. Private rooms are preferred over shared rooms
  2. Recommendations After Buying the Property
    1. The investor should focus on providing a home like feel and experience to the guests as opposed to just renting out a property
    2. Properly stating all essential services like internet and heating, describing each aspect of the property such as what areas are accessible etc, will be appreciated by the guests
    3. The investor could consider hiring a full time employee who greets and interacts well with the hosts.
    4. The host’s responses should be prompt, preferably within one hour
    5. The host should aim to achieve superhost status
    6. Special emphasis should be laid on appropriately advertising/marketing the available services on the listing
    7. The full time employee should be responsible for managing the property and provide complete support to the guests
    8. The property should be family friendly

This kind of customized experience in turn will translate into high booking rates and increased profits.

REFERENCES

[1]  https://www.citylab.com/equity/2018/03/what-airbnb-did-to-new-york-city/552749/45ad8a941a5a
[2]  https://smartasset.com/mortgage/where-do-airbnb-hosts-make-the-most-money 
[3]  https://ny.curbed.com/2019/12/13/21009872/nyc-home-value-2010s-manhattan-apartments
LS0tCnRpdGxlOiAiVGVhbSAxMCBBaXJCbkItIE5ldyBZb3JrIE1hcmtldCIKb3V0cHV0OgogIHBkZl9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgojIyBEYXRhIE1pbmluZyBhbmQgUHJlZGljdGl2ZSBBbmFseXRpY3MKCiMjIyBQcm9qZWN0IHRpdGxlOiBFeHBsb3JhdG9yeSBhbmQgUHJlZGljdGl2ZSBBbmFseXNpcyBPZiBBaXJibmIgRGF0YQoKIyMjIE1hcmtldCBhc3NpZ25lZCB0byB0aGUgdGVhbTogTmV3IFlvcmsgQ2l0eQoKKiJXZSwgdGhlIHVuZGVyc2lnbmVkLCBjZXJ0aWZ5IHRoYXQgdGhlIHJlcG9ydCBzdWJtaXR0ZWQgaXMgb3VyIG9yaWdpbmFsIHdvcms7IGFsbCBhdXRob3JzIHBhcnRpY2lwYXRlZCBpbiB0aGUgd29yayBpbiBhIHN1YnN0YW5zaXZlIHdheTsgYWxsIGF1dGhvcnMgaGF2ZSBzZWVuIGFuZCBhcHByb3ZlZCB0aGUgcmVwb3J0IGFzIHN1Ym1pdHRlZDsgdGhlIHRleHQsIGltYWdlcywgaWxsdXN0cmF0aW9ucywgYW5kIG90aGVyIGl0ZW1zIGluY2x1ZGVkIGluIHRoZSBtYW51c2NyaXB0IGRvIG5vdCBjYXJyeSBhbnkgaW5ncmluZ2VtZW50L3BsYWdpYXJpc20gaXNzdWUgdXBvbiBhbnkgZXhpc3RpbmcgY29weXJpZ2h0ZWQgbWF0ZXJpYWxzLiIqCgpUZWFtIE1lbWJlciAgfCBOYW1lcyBvZiBTaWduZWQgVGVhbSBNZW1iZXJzCi0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tCkNvbnRhY3QgTWVtYmVyICB8IENoaW5jaHdhZGUsIE5pa2l0YQpUZWFtIE1lbWJlciAyICB8IEdveWFsLCBQdXNoa2FyClRlYW0gTWVtYmVyIDMgIHwgS29tYXJyYWp1LCBNZWdoYQpUZWFtIE1lbWJlciA0ICB8IE5hcGhhZGUsIENoaW5tYXkKVGVhbSBNZW1iZXIgNSAgfCBTYXJhaXlhLCBQYXJ0aApUZWFtIE1lbWJlciA2ICB8IFNpbmdoLCBQcmFjaGkKCiMjIyMgRVhFQ1VUSVZFIFNVTU1BUlk6CgpBaXJibmJzIGluIE5ldyBZb3JrIENpdHkgaGF2ZSB0aGUgc2Vjb25kIGhpZ2hlc3QgaG9zdCBwcm9maXRzIG91dCBvZiBhbGwgdGhlIGNpdGllcyBpbiB0aGUgVVMsIHdpdGggcmV2ZW51ZXMgZ29pbmcgdXAgdG8gJDc0MSwxNjcsMjk2LiBBdmVyYWdlIHByaWNlIG9mIGEgTmV3IFlvcmsgQWlyYm5iIGxpc3RpbmcgaXMgJDE1Ny4gVGhlIG1hcmtldCBjb25zaXN0cyBvZiA1IGJvcm91Z2hzOiBNYW5oYXR0YW4sIEJyb29rbHluLCBRdWVlbnMsIHRoZSBCcm9ueCBhbmQgU3RhdGVuIElzbGFuZC4KCiMjIyMgR09BTFM6CjEuIFRvIGlkZW50aWZ5IHRoZSBpbXBvcnRhbnQgZmVhdHVyZXMgdGhhdCBjb250cmlidXRlIHRvIGFjaGlldmluZyBhIGhpZ2ggYm9va2luZyByYXRlIG9uIEFpcmJuYiwgd2l0aCBzcGVjaWFsIGVtcGhhc2lzIG9uIHByb3RlY3RpbmcgdGhlIGludmVzdG9yIGZyb20gaW52ZXN0aW5nIGluIGEgcHJvcGVydHkgdGhhdCB3b3VsZCB5aWVsZCBsb3cgYm9va2luZ3Mgd2hlbiBwbGFjZWQgb24gdGhlIEFpcmJuYiBtYXJrZXQuCjIuIEJ1aWxkIGFuIGVmZmljaWVudCBwcmVkaWN0aXZlIG1vZGVsIHVzaW5nIHRoZSB0cmFpbmluZyBkYXRhIHRvIHF1YW50aWZ5IG91ciBhbmFseXNpcyBieSB1c2luZyB0aGUgbW9zdCBhcHByb3ByaWF0ZSBwZXJmb3JtYW5jZSBtZXRyaWMuCgojIyMjIFJFU0VBUkNIIFFVRVNUSU9OUzoKMS4gQnV5aW5nIGFuZCBtYWludGFpbmluZyBwcm9wZXJ0aWVzIGluIE5ZQyBpcyBleHBlbnNpdmUuRm9yIGEgcmlzayBhdmVyc2UgaW52ZXN0b3IsIHRoZSBjb3N0cyBhc3NvY2lhdGVkIHdpdGggYW4gdW5wcm9maXRhYmxlIGludmVzdG1lbnQgaXMgc2lnbmlmaWNhbnQgYW5kIGNhbiBpbmN1ciBhIGdyZWF0ZXIgbG9zcyBhcyBjb21wYXJlZCB0byB0aGUgY29zdCBhc3NvY2lhdGVkIHdpdGggbWlzc2luZyBvdXQgb24gYSBwb3RlbnRpYWxseSBwcm9maXRhYmxlIG9wcG9ydHVuaXR5LklzIGl0IHBvc3NpYmxlIHRvIG1pbmltaXplIHRoZSByaXNrIGFzc29jaWF0ZWQgd2l0aCBmYWxzZWx5IGNsYXNzaWZ5aW5nIGEgcHJvcGVydHkgd2l0aCBhIGxvdyBib29raW5nIHJhdGUgYXMgb25lIHdpdGggYSBoaWdoIGJvb2tpbmcgcmF0ZSA/CgoyLiBJbiBvcmRlciB0byBhY2hpZXZlIGEgaGlnaCBib29raW5nIHJhdGUsIHdoYXQgb3VyIHRoZSBmYWN0b3JzIHRoYXQgdGhlIGludmVzdG9yIG11c3QgY29uc2lkZXI6CiAgICBhKSBCZWZvcmUgYnV5aW5nIHRoZSBwcm9wZXJ0eT8KICAgIGIpIEFmdGVyIGJ1eWluZyB0aGUgcHJvcGVydHk/CiAgICAKIyMjIyBNRVRIT0RPTE9HWToKMS4gRGF0YSBQcmVwYXJhdGlvbjotCiAgICBhKSBGaWx0ZXJpbmcgdGhlIGRhdGEgYmFzZWQgb25lIHRoZSByYW5kb21jb250cm9sIHZhcmlhYmxlIGFuZCBjb250cmFzdGluZyBpdCB3aXRoIHNpbXBsZSBzdHJpbmcgcHJvY2Vzc2luZyBvbiB0aGUgbWFya2V0IHZhcmlhYmxlCiAgICBiKSBTcGxpdHRpbmcgdGhlIGFtZW5pdGllcyBpbnRvIGR1bW15IGNvbHVtbnMgZm9yIGluZGl2aXVhbCBwcm9kdWN0cyBhbmQgc2VydmljZXMKICAgIGMpIFJlbW92aW5nIHRoZSAnJCcgc3ltYm9sIGZyb20gdGhlIHNvbWUgb2YgdGhlIGNvbHVtbnMgYW5kIGNvbnZlcnRpbmcgaW50byBudW1lcmljCiAgICBkKSBDb252ZXJ0aW5nIGhvc3QgcmVzcG9uc2UgcmF0ZSBwZXJjZW50YWdlcyB0byBudW1lcmljCiAgICBlKSBDb252ZXJ0aW5nIGRpc2NyZXRlIHZhcmlhYmxlcyB0byBmYWN0b3JzCiAgICBmKSBSZWNvZGluZyBzb21lIG9mIHRoZSBmYWN0b3IgdmFyaWFibGVzIGJhc2VkIG9uIGRvbWFpbiBrbm93bGVkZ2UKICAgIGcpIERlYWxpbmcgd2l0aCBOL0EgdmFsdWVzIGJhc2VkIG9uIGRvbWFpbiBrbm93bGVkZ2UKICAgIGgpIEZvciBwcm9wZXJ0eV90eXBlcyB3aXRoIGxlc3MgdGhhbiBhIDEwMDAgb2JzZXJ2YXRpb25zLCBjcmVhdGUgYW5vdGhlciBmYWN0b3IgbGV2ZWwuCiAgICBpKSBDb252ZXJ0aW5nIHRleHQgYmFzZWQgZGVzY3JpcHRpdmUgY29sdW1ucyBpbnRvIGZhY3RvcnMuIFRoZSBpZGVhIGJlaGluZCB0aGlzIHdhcyB0byBiZSBhYmxlIHRvIHJlZHVjZSBjb21wbGV4aXR5LlRoaXMgd291bGQgY2hlY2sgdGhlIGltcG9ydGFuY2Ugb2YgcHJvcGVybHkgZGVzY3JpYmluZyBhc3BlY3RzIG9mIHRoZSBwcm9wZXJ0aWVzIHRvIGd1ZXN0cy4KICAgIGopIFNwZWNpZnlpbmcgbWlzc2luZyBudW1iZXIgb2YgYmVkcm9vbXMgYW5kIGJhdGhyb29tcyBhcyB6ZXJvCiAgICBrKSBTcGVjaWZ5aW5nIG1pc3NpbmcgY2xlYW5pbmcgZmVlIGFzIG1lYW4KICAgIGwpIFNwbGl0dGluZyB0aGUgbWFya2V0IGludG8gYm9yb3VnaHMgYmFzZWQgb24gemlwY29kZXMgYW5kIG5laWdoYm91cmhvb2RzIGluY2FzZSBvZiBtaXNzaW5nIHZhbHVlcy4KICAgIApXZSBpbmNsdWRlZCB0aGUgZGF0YSBwcmVwcm9jZXNzaW5nIGZyb20gdGhlIEthZ2dsZSBDb21wZXRpdGlvbi4gCgogIApgYGB7ciwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSgidGlkeXZlcnNlIikKbGlicmFyeSgidGlkeW1vZGVscyIpCmxpYnJhcnkoInBsb3RseSIpCmxpYnJhcnkoInNraW1yIikKbGlicmFyeSgibHVicmlkYXRlIikKbGlicmFyeSgiY2FyIikKbGlicmFyeSgiY2FyZXQiKQpsaWJyYXJ5KCJjb3dwbG90IikKbGlicmFyeSgiZmFzdER1bW1pZXMiKQpsaWJyYXJ5KCJzcGxpdHN0YWNrc2hhcGUiKQpsaWJyYXJ5KCJ3cmFwciIpCgpgYGAKCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQpvcHRpb25zKHlhcmRzdGljay5ldmVudF9maXJzdCA9IEZBTFNFKQpgYGAKCmBgYHtyIEltcG9ydGluZyBkYXRhLCByZXN1bHRzPSJoaWRlIn0KZGZhVHJhaW4gPC0gcmVhZF9jc3YoImFpcmJuYlRyYWluLmNzdiIpCmRmYVRlc3QgPC0gcmVhZF9jc3YoImFpcmJuYlRlc3QuY3N2IikKYGBgCmBgYHtyfQojVXNpbmcgc3RyaW5nIHByb2Nlc3NpbmcgdG8gZmlsdGVyIE5ldyBZb3JrIGRhdGEKZGZhVHJhaW5OZXdZb3JrIDwtIGRmYVRyYWluICU+JSAKICBmaWx0ZXIobWFya2V0ID09ICJOZXcgWW9yayIpCgojVXNpbmcgUmFuZG9tIENvbnRyb2wgdmFyaWFibGUgdG8gZmlsdGVyIE5ldyBZb3JrIGRhdGEKZGZhVHJhaW48LWRmYVRyYWluICU+JSBmaWx0ZXIoYXMuaW50ZWdlcihkZmFUcmFpbiRge3JhbmRvbUNvbnRyb2x9YC8xMDAwKT09MTE2KQoKZGZhVHJhaW5EdXAgPC0gZGZhVHJhaW4KYGBgCgpgYGB7cn0KI1RoZXkgc2VlbSB0byBoYXZlIHRoZSBzYW1lIHVuaXF1ZSBzdGF0ZXMKdW5pcXVlKGRmYVRyYWluTmV3WW9yayRzdGF0ZSkKYGBgCgoKYGBge3J9CnVuaXF1ZShkZmFUcmFpbiRzdGF0ZSkKYGBgCmBgYHtyfQojSG93ZXZlciwgZGZhVHJhaW4gKGZpbHRlcmVkIHdpdGggUmFuZG9tIENvbnRyb2wpIGhhcyBvbmx5IG9uZSBOZXcgSmVyc2V5IHppcGNvZGUKZGZhVHJhaW5OSiA8LSBkZmFUcmFpbiAlPiUgZmlsdGVyKHN0YXRlPT0iTkoiKSAKdW5pcXVlKGRmYVRyYWluTkokemlwY29kZSkKYGBgCgoKYGBge3J9CiNkZmFUcmFpbk5ld1lvcmsgKGZpbHRlcmVkIHdpdGggc3RyaW5nIHByb2Nlc3NpbmcpIGhhcyBtYW55IE5ldyBKZXJzZXkgemlwY29kZXMKZGZhVHJhaW5OZXdZb3JrTkogPC0gZGZhVHJhaW5OZXdZb3JrICU+JSBmaWx0ZXIoc3RhdGU9PSJOSiIpCnVuaXF1ZShkZmFUcmFpbk5ld1lvcmtOSiR6aXBjb2RlKQpgYGAKCgpgYGB7cn0Kc2tpbShkZmFUcmFpbikKYGBgCgoKCmBgYHtyfQojU3BsaXR0aW5nIGFtZW5pdGllcwpzYW1wbGVkZiA8LSBkZmFUcmFpbiAlPiUgCiAgc2VsZWN0KGlkLCBhbWVuaXRpZXMpICU+JSAKICBtdXRhdGUoYW1lbml0aWVzID0gZ3N1YignW3tdJywgJycsIChnc3ViKCdbfV0nLCAnJywgYW1lbml0aWVzKSkpKQoKZGYxIDwtIGNTcGxpdF9lKHNhbXBsZWRmLCAnYW1lbml0aWVzJywgJywnLCB0eXBlPSAnY2hhcmFjdGVyJywgZmlsbD0wLCBkcm9wPVRSVUUpCm5hbWVzKGRmMSkgPC0gIHN1YignLipfJywgJycsIG5hbWVzKGRmMSkpCgppIDwtIChjb2xTdW1zKEZpbHRlcihpcy5udW1lcmljLCBkZjEpKSA+IDEwMDApCmRmMSA8LSBkZjFbLGldCgpkZjEgPC0gCiAgZGYxICU+JSAKICBtdXRhdGVfYXQobmFtZXMoZGYxKVstMV0gLCB+ZmFjdG9yKC4pKQoKZGZhVHJhaW4gPC0gbWVyZ2UoZGZhVHJhaW4sIGRmMSwgYnkueCA9ICJpZCIsIGJ5LnkgPSAiaWQiKQoKYGBgCgoKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KI1JlbW92aW5nIHRoZSAnJCcgc3ltYm9sCmRmYVRyYWluIDwtCiAgZGZhVHJhaW4gJT4lCiAgbXV0YXRlKAogICAgIGNsZWFuaW5nX2ZlZSA9IGFzLm51bWVyaWMoZ3N1YignWyRdJywgJycsIChnc3ViKCdbLF0nLCAnJywgY2xlYW5pbmdfZmVlKSkpKSwKICAgIGV4dHJhX3Blb3BsZSA9IGFzLm51bWVyaWMoZ3N1YignWyRdJywgJycsIChnc3ViKCdbLF0nLCAnJywgZXh0cmFfcGVvcGxlKSkpKSwKICAgIHByaWNlID0gYXMubnVtZXJpYyhnc3ViKCdbJF0nLCAnJywgKGdzdWIoJ1ssXScsICcnLCBwcmljZSkpKSksCiAgICBzZWN1cml0eV9kZXBvc2l0ID1hcy5udW1lcmljKGdzdWIoJ1skXScsICcnLCAoZ3N1YignWyxdJywgJycsIHNlY3VyaXR5X2RlcG9zaXQpKSkpCiAgKQojQ29udmVydGluZyBwZXJjZW50YWdlIHRvIG51bWVyaWMgCmRmYVRyYWluIDwtCiAgZGZhVHJhaW4gJT4lCiAgbXV0YXRlKGhvc3RfcmVzcG9uc2VfcmF0ZSA9IGFzLm51bWVyaWMoZ3N1YignWyVdJywgJycsIGhvc3RfcmVzcG9uc2VfcmF0ZSkpKSAlPiUKICBtdXRhdGUoaG9zdF9yZXNwb25zZV9yYXRlID0gaG9zdF9yZXNwb25zZV9yYXRlIC8gMTAwKQpgYGAKCmBgYHtyfQojQ29udmVydGluZyBkaXNjcmV0ZSB2YXJpYWJsZXMgdG8gZmFjdG9ycwpjb2xzX3RvX2ZhY3RvciA8LSBjKAogICJjYW5jZWxsYXRpb25fcG9saWN5IiwKICAiaG9zdF9yZXNwb25zZV90aW1lIiwKICAicHJvcGVydHlfdHlwZSIsCiAgInJvb21fdHlwZSIsCiAgImhvc3RfaWRlbnRpdHlfdmVyaWZpZWQiLAogICJob3N0X2lzX3N1cGVyaG9zdCIsCiAgImluc3RhbnRfYm9va2FibGUiLAogICJpc19sb2NhdGlvbl9leGFjdCIsCiAgInJlcXVpcmVzX2xpY2Vuc2UiLAogICJiZWRfdHlwZSIKKQoKZGZhVHJhaW4gPC0gCiAgZGZhVHJhaW4gJT4lIAogIG11dGF0ZV9hdChjb2xzX3RvX2ZhY3RvciwgfmZhY3RvciguKSkKYGBgCgpgYGB7cn0KI1JlY29kaW5nIGJlZF90eXBlIGZhY3RvciBsZXZlbHMKZGZhVHJhaW4gPC0gZGZhVHJhaW4gJT4lCiAgbXV0YXRlKAogICAgYmVkX3R5cGUgPSByZWNvZGVfZmFjdG9yKAogICAgICBiZWRfdHlwZSwKICAgICAgQWlyYmVkID0gIk5vdCBhIFJlYWwgQmVkIiwKICAgICAgQ291Y2ggPSAiTm90IGEgUmVhbCBCZWQiLAogICAgICBGdXRvbiA9ICJOb3QgYSBSZWFsIEJlZCIsCiAgICAgIGBQdWxsLW91dCBTb2ZhYCA9ICJOb3QgYSBSZWFsIEJlZCIKICAgICkpCiNSZWNvZGluZyBjYW5jZWxsYXRpb25fcG9saWN5IGZhY3RvciBsZXZlbHMgIApkZmFUcmFpbiA8LSBkZmFUcmFpbiAlPiUKICBtdXRhdGUoCiAgICBjYW5jZWxsYXRpb25fcG9saWN5ID0gcmVjb2RlX2ZhY3RvcigKICAgICAgY2FuY2VsbGF0aW9uX3BvbGljeSwKICAgICAgbHV4dXJ5X25vX3JlZnVuZCA9ICJsdXh1cnkiLAogICAgICBsdXh1cnlfc3VwZXJfc3RyaWN0XzEyNSA9ICJsdXh1cnkiLAogICAgICBsdXh1cnlfc3VwZXJfc3RyaWN0Xzk1ID0gImx1eHVyeSIsCiAgICAgIGx1eHVyeV9tb2RlcmF0ZSA9ICJsdXh1cnkiLAogICAgICBzdHJpY3QgPSAibHV4dXJ5IgogICAgKSkKCiNSZW1vdmluZyBOQXMKZGZhVHJhaW4gPC0gCiAgZGZhVHJhaW4gJT4lIAogICAgcmVwbGFjZV9uYShsaXN0KGhvc3RfcmVzcG9uc2VfdGltZSA9ICJOL0EiLCBob3N0X2lzX3N1cGVyaG9zdCA9IEZBTFNFLCBob3N0X2lkZW50aXR5X3ZlcmlmaWVkID0gRkFMU0UpKQoKYGBgCgoKYGBge3J9CiMgQ3JlYXRpbmcgb3RoZXIgcHJvcGVydHlfdHlwZQpjb21iaW5lZFByb3BlcnR5VHlwZSA8LSBwdWxsKGRmYVRyYWluICU+JSAKICBncm91cF9ieShwcm9wZXJ0eV90eXBlKSAlPiUgCiAgdGFsbHkoKSAlPiUgCiAgZmlsdGVyKG4gPCAxMDAwKSAlPiUgCiAgYXJyYW5nZShuKSAlPiUgCiAgc2VsZWN0KHByb3BlcnR5X3R5cGUpICU+JSAKICBtdXRhdGUocHJvcGVydHlfdHlwZSA9IGFzLmNoYXJhY3Rlcihwcm9wZXJ0eV90eXBlKSksIHByb3BlcnR5X3R5cGUpCgpkZmFUcmFpbiA8LSBkZmFUcmFpbiAlPiUgCiAgbXV0YXRlKHByb3BlcnR5X3R5cGUgPSBmY3RfY29sbGFwc2UocHJvcGVydHlfdHlwZSwgT3RoZXIgPSBjb21iaW5lZFByb3BlcnR5VHlwZSkpCgpgYGAKCgoKCmBgYHtyfQojQ29udmVydGluZyB0ZXh0IGJhc2VkIGNvbHVtbnMgaW50byBmYWN0b3JzCmRmYVRyYWluIDwtIGRmYVRyYWluICU+JSAKICBtdXRhdGUoaG9zdF9hYm91dCA9IGlmZWxzZShpcy5uYShob3N0X2Fib3V0KSwiRkFMU0UiLCAiVFJVRSIpKQpkZmFUcmFpbiA8LSBkZmFUcmFpbiAlPiUgCiAgbXV0YXRlKGludGVyYWN0aW9uID0gaWZlbHNlKGlzLm5hKGludGVyYWN0aW9uKSwiRkFMU0UiLCAiVFJVRSIpKQpkZmFUcmFpbiA8LSBkZmFUcmFpbiAlPiUgCiAgbXV0YXRlKG5laWdoYm9yaG9vZF9vdmVydmlldyA9IGlmZWxzZShpcy5uYShuZWlnaGJvcmhvb2Rfb3ZlcnZpZXcpLCJGQUxTRSIsICJUUlVFIikpCmRmYVRyYWluIDwtIGRmYVRyYWluICU+JSAKICBtdXRhdGUodHJhbnNpdCA9IGlmZWxzZShpcy5uYSh0cmFuc2l0KSwiRkFMU0UiLCAiVFJVRSIpKQoKZGZhVHJhaW4gPC0gZGZhVHJhaW4gJT4lIAogIG11dGF0ZShhY2Nlc3MgPSBpZmVsc2UoaXMubmEoYWNjZXNzKSwiRkFMU0UiLCAiVFJVRSIpKQoKZGZhVHJhaW4gPC0gZGZhVHJhaW4gJT4lIAogIG11dGF0ZShob3VzZV9ydWxlcyA9IGlmZWxzZShpcy5uYShob3VzZV9ydWxlcyksIkZBTFNFIiwgIlRSVUUiKSkKCgpkZmFUcmFpbiA8LSBkZmFUcmFpbiAlPiUgCiAgbXV0YXRlKG5vdGVzID0gaWZlbHNlKGlzLm5hKG5vdGVzKSwiRkFMU0UiLCAiVFJVRSIpKQoKCmRmYVRyYWluIDwtIGRmYVRyYWluICU+JSAKICBtdXRhdGUoc3BhY2UgPSBpZmVsc2UoaXMubmEoc3BhY2UpLCJGQUxTRSIsICJUUlVFIikpCgpjb2xzX3RvX2ZhY3RvciA8LSBjKAogICJob3N0X2Fib3V0IiwKICAic3BhY2UiLAogICJub3RlcyIsCiAgImhvdXNlX3J1bGVzIiwKICAiYWNjZXNzIiwKICAidHJhbnNpdCIsCiAgIm5laWdoYm9yaG9vZF9vdmVydmlldyIsCiAgImludGVyYWN0aW9uIgopCgpkZmFUcmFpbiA8LSAKICBkZmFUcmFpbiAlPiUgCiAgbXV0YXRlX2F0KGNvbHNfdG9fZmFjdG9yLCB+ZmFjdG9yKC4pKQoKYGBgCgpgYGB7cn0KI0ltcHV0aW5nIE5BIHZhbHVlcwpkZmFUcmFpbiRiZWRyb29tc1tpcy5uYShkZmFUcmFpbiRiZWRyb29tcyldID0wCmRmYVRyYWluJGJhdGhyb29tc1tpcy5uYShkZmFUcmFpbiRiYXRocm9vbXMpXSA9MApkZmFUcmFpbiA8LSBkZmFUcmFpbiAlPiUgZ3JvdXBfYnkobWFya2V0KSAlPiUKbXV0YXRlKGNsZWFuaW5nX2ZlZT1pZmVsc2UoaXMubmEoY2xlYW5pbmdfZmVlKSxtZWFuKGNsZWFuaW5nX2ZlZSxuYS5ybT1UUlVFKSxjbGVhbmluZ19mZWUpKSAlPiUgdW5ncm91cCgpCmBgYAoKCmBgYHtyfQojRGl2aWRpbmcgaW50byBib3JvdWdocwpkZmFUcmFpbiA8LSBkZmFUcmFpbiAlPiUgbXV0YXRlKGJvcm91Z2g9aWZlbHNlKHN1YnN0cihkZmFUcmFpbiR6aXBjb2RlLCAxLCAzKSAgPT0xMDAgfCBzdWJzdHIoZGZhVHJhaW4kemlwY29kZSwgMSwgMykgID09MTAxIHwgc3Vic3RyKGRmYVRyYWluJHppcGNvZGUsIDEsIDMpICA9PTEwMiwgIk1hbmhhdHRhbiIgLCBpZmVsc2Uoc3Vic3RyKGRmYVRyYWluJHppcGNvZGUsIDEsIDMpICA9PTExMiAgLCJCcm9va2x5biIsCmlmZWxzZShzdWJzdHIoZGZhVHJhaW4kemlwY29kZSwgMSwgMykgID09MTExIHwgc3Vic3RyKGRmYVRyYWluJHppcGNvZGUsIDEsIDMpICA9PTExMyAgfCBzdWJzdHIoZGZhVHJhaW4kemlwY29kZSwgMSwgMykgID09MTE0IHwgc3Vic3RyKGRmYVRyYWluJHppcGNvZGUsIDEsIDMpICA9PTExMCB8IHN1YnN0cihkZmFUcmFpbiR6aXBjb2RlLCAxLCAzKSAgPT0xMTYgLCJRdWVlbnMiLAppZmVsc2Uoc3Vic3RyKGRmYVRyYWluJHppcGNvZGUsIDEsIDMpICA9PTEwMyAsIlN0YXRlbiBJc2xhbmQiLAppZmVsc2Uoc3Vic3RyKGRmYVRyYWluJHppcGNvZGUsIDEsIDMpICA9PTEwNCAgLCJCcm9ueCIsIk5BIikpKSkpKQpgYGAKCmBgYHtyfQpkZlRyYWluTm9aaXAgPC1kZmFUcmFpbiAlPiUgZmlsdGVyKGlzLm5hKHppcGNvZGUpKQpgYGAKCgpgYGB7cn0KZGZUcmFpbk5vWmlwJGJvcm91Z2ggPC0gZGZhVHJhaW4kYm9yb3VnaFttYXRjaChkZlRyYWluTm9aaXAkbmVpZ2hib3VyaG9vZCwgZGZhVHJhaW4kbmVpZ2hib3VyaG9vZCldCmBgYAoKYGBge3J9CmZpbmFsIDwtIG1lcmdlKGRmYVRyYWluLCBkZlRyYWluTm9aaXBbICwgYygiaWQiLCAiYm9yb3VnaCIpXSwgYnkgPSAiaWQiLCBhbGwgPSBUUlVFKQpmaW5hbCRib3JvdWdoIDwtIGZpbmFsJGJvcm91Z2gueCAlPyUgZmluYWwkYm9yb3VnaC55CmBgYAoKYGBge3J9CmZpbmFsIDwtIGZpbmFsICU+JSBzZWxlY3QoLWMoImJvcm91Z2gueCIsImJvcm91Z2gueSIpKQpkZmFUcmFpbiA8LSBmaW5hbCAlPiUgZmlsdGVyKGJvcm91Z2ghPSJOQSIpCmhlYWQoZGZhVHJhaW4pCmBgYAoKCiMjIyBPdmVydmlldyBvZiBwcm9wZXJ0aWVzIGFjcm9zcyBOZXcgWW9yayBzcGxpdCBieSBib3JvdWdocwpFeHRyYWN0aW5nIFN1YndheSBEYXRhOgpgYGB7cn0KZGZ6aXBTIDwtIHJlYWRfY3N2KCJOWXN1YndheTEuY3N2IikKc2tpbShkZnppcFMpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiNEaXZpZGluZyBpbnRvIGJvcm91Z2hzCmRmYVRyYWluRHVwIDwtIGRmYVRyYWluRHVwICU+JSBtdXRhdGUoYm9yb3VnaD1pZmVsc2Uoc3Vic3RyKGRmYVRyYWluRHVwJHppcGNvZGUsIDEsIDMpICA9PTEwMCB8IHN1YnN0cihkZmFUcmFpbkR1cCR6aXBjb2RlLCAxLCAzKSAgPT0xMDEgfCBzdWJzdHIoZGZhVHJhaW5EdXAkemlwY29kZSwgMSwgMykgID09MTAyLCAiTWFuaGF0dGFuIiAsIGlmZWxzZShzdWJzdHIoZGZhVHJhaW5EdXAkemlwY29kZSwgMSwgMykgID09MTEyICAsIkJyb29rbHluIiwKaWZlbHNlKHN1YnN0cihkZmFUcmFpbkR1cCR6aXBjb2RlLCAxLCAzKSAgPT0xMTEgfCBzdWJzdHIoZGZhVHJhaW5EdXAkemlwY29kZSwgMSwgMykgID09MTEzICB8IHN1YnN0cihkZmFUcmFpbkR1cCR6aXBjb2RlLCAxLCAzKSAgPT0xMTQgfCBzdWJzdHIoZGZhVHJhaW5EdXAkemlwY29kZSwgMSwgMykgID09MTEwIHwgc3Vic3RyKGRmYVRyYWluRHVwJHppcGNvZGUsIDEsIDMpICA9PTExNiAsIlF1ZWVucyIsCmlmZWxzZShzdWJzdHIoZGZhVHJhaW5EdXAkemlwY29kZSwgMSwgMykgID09MTAzICwiU3RhdGVuIElzbGFuZCIsCmlmZWxzZShzdWJzdHIoZGZhVHJhaW5EdXAkemlwY29kZSwgMSwgMykgID09MTA0ICAsIkJyb254IiwiTkEiKSkpKSkpCgoKZGZUcmFpbk5vWmlwIDwtZGZhVHJhaW5EdXAgJT4lIGZpbHRlcihpcy5uYSh6aXBjb2RlKSkKCgoKCmRmVHJhaW5Ob1ppcCRib3JvdWdoIDwtIGRmYVRyYWluRHVwJGJvcm91Z2hbbWF0Y2goZGZUcmFpbk5vWmlwJG5laWdoYm91cmhvb2QsIGRmYVRyYWluRHVwJG5laWdoYm91cmhvb2QpXQoKCmZpbmFsIDwtIG1lcmdlKGRmYVRyYWluRHVwLCBkZlRyYWluTm9aaXBbICwgYygiaWQiLCAiYm9yb3VnaCIpXSwgYnkgPSAiaWQiLCBhbGwgPSBUUlVFKQpmaW5hbCRib3JvdWdoIDwtIGZpbmFsJGJvcm91Z2gueCAlPyUgZmluYWwkYm9yb3VnaC55CgoKCmZpbmFsIDwtIGZpbmFsICU+JSBzZWxlY3QoLWMoImJvcm91Z2gueCIsImJvcm91Z2gueSIpKQpkZmFUcmFpbkR1cCA8LSBmaW5hbCAlPiUgZmlsdGVyKGJvcm91Z2ghPSJOQSIpCgpkZmFUcmFpbkR1cCA8LSBkZmFUcmFpbkR1cCAlPiUgbXV0YXRlKHRvdGFsX2FtZW5pdGllcyA9IGlmZWxzZShuY2hhcihhbWVuaXRpZXMpPjIsIHN0cl9jb3VudChhbWVuaXRpZXMsICcsJykrMSwgMCkpCgpgYGAKCk1hcCBmb3IgTWFuaGF0dGFuIEJvcm91Z2g6CmBgYHtyfQpkZk0gPC0gZGZhVHJhaW5EdXAgJT4lIGZpbHRlcihib3JvdWdoPT0nTWFuaGF0dGFuJykgCmBgYApgYGB7cn0KbGlicmFyeSgibGVhZmxldCIpCgpgYGAKCgpgYGB7cn0KI0NyZWF0aW5nIExpc3RpbmdzIGFjcm9zcyBOWUMKbGVhZmxldChkcGx5cjo6YmluZF9yb3dzKGRmTSxkZnppcFMpKSAlPiUKIGFkZFRpbGVzKCkgJT4lCiBhZGRDaXJjbGVNYXJrZXJzKGRhdGE9ZGZNLCBjb2xvciA9ICcjOUQ3JywKICAgIG9wYWNpdHkgPSAxLH5kZk0kbG9uZ2l0dWRlLCB+ZGZNJGxhdGl0dWRlLGxhYmVsT3B0aW9ucyA9IGxhYmVsT3B0aW9ucyhub0hpZGUgPSBGKSxjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkscG9wdXAgPSBwYXN0ZTAoICI8YnI+IDxiPiBQcmljZTogPC9iPiIsIGRmTSRwcmljZSwgIjxici8+PGI+IFJvb20gVHlwZTogPC9iPiIsIGRmTSRyb29tX3R5cGUsICI8YnIvPjxiPiBQcm9wZXJ0eSBUeXBlOiA8L2I+IiwgZGZNJHByb3BlcnR5X3R5cGUsIjxicj4gPGI+IEFtZW5pdGllczogPC9iPiIsIGRmTSR0b3RhbF9hbWVuaXRpZXMsIjxicj4gPGI+IFJldmlldyBTY29yZTogPC9iPiIsIGRmTSRyZXZpZXdfc2NvcmVzX3ZhbHVlLCI8YnI+IDxiPiBBbWVuaXRpZXM6IDwvYj4iLCBkZk0kdG90YWxfYW1lbml0aWVzLCI8YnI+IDxiPiBCb29raW5nIFJhdGU6IDwvYj4iLCBkZk0kaGlnaF9ib29raW5nX3JhdGUKICAgICAgICAgICApKSAlPiUgCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhPWRmemlwUyx+bG9uZ2l0dWRlLCB+bGF0aXR1ZGUsY29sb3IgPSAnI0ZBNScsbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IEYpLGNsdXN0ZXJPcHRpb25zID0gbWFya2VyQ2x1c3Rlck9wdGlvbnMoKSxvcGFjaXR5ID0gMSxwb3B1cCA9IHBhc3RlMCggIjxicj4gPGI+IFN0YXRpb24gTmFtZTogPC9iPiIsIGRmemlwUyRTdGF0aW9ubmFtZSkpICU+JSAKICBzZXRWaWV3KC03NC4wMCwgNDAuNzEsIHpvb20gPSAxMikKICMlPiUKICAjYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpCmBgYAoKCgpNYXAgZm9yIEJyb29rbHluIEJvcm91Z2g6CmBgYHtyfQpkZkIgPC0gZGZhVHJhaW5EdXAgJT4lIGZpbHRlcihib3JvdWdoPT0nQnJvb2tseW4nKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KCJsZWFmbGV0IikKI0NyZWF0aW5nIExpc3RpbmdzIGFjcm9zcyBOWUMKbGVhZmxldChkcGx5cjo6YmluZF9yb3dzKGRmQixkZnppcFMpKSAlPiUKIGFkZFRpbGVzKCkgJT4lCiBhZGRDaXJjbGVNYXJrZXJzKGRhdGE9ZGZCLCBjb2xvciA9ICcjOUQ3JywKICAgIG9wYWNpdHkgPSAxLH5sb25naXR1ZGUsIH5sYXRpdHVkZSxsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMobm9IaWRlID0gRiksY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucygpLHBvcHVwID0gcGFzdGUwKCAiPGJyPiA8Yj4gUHJpY2U6IDwvYj4iLCBkZkIkcHJpY2UsICI8YnIvPjxiPiBSb29tIFR5cGU6IDwvYj4iLCBkZkIkcm9vbV90eXBlLCAiPGJyLz48Yj4gUHJvcGVydHkgVHlwZTogPC9iPiIsIGRmQiRwcm9wZXJ0eV90eXBlLCI8YnI+IDxiPiBBbWVuaXRpZXM6IDwvYj4iLCBkZkIkdG90YWxfYW1lbml0aWVzLCI8YnI+IDxiPiBSZXZpZXcgU2NvcmU6IDwvYj4iLCBkZkIkcmV2aWV3X3Njb3Jlc192YWx1ZSwiPGJyPiA8Yj4gQW1lbml0aWVzOiA8L2I+IiwgZGZCJHRvdGFsX2FtZW5pdGllcywiPGJyPiA8Yj4gQm9va2luZyBSYXRlOiA8L2I+IiwgZGZCJGhpZ2hfYm9va2luZ19yYXRlCiAgICAgICAgICAgKSkgJT4lIAogIGFkZENpcmNsZU1hcmtlcnMoZGF0YT1kZnppcFMsY29sb3IgPSAnI0ZBNScsb3BhY2l0eSA9IDEscG9wdXAgPSBwYXN0ZTAoICI8YnI+IDxiPiBTdGF0aW9uIE5hbWU6IDwvYj4iLCBkZnppcFMkU3RhdGlvbm5hbWUpKSAlPiUgCiBzZXRWaWV3KC03NC4wMCwgNDAuNzEsIHpvb20gPSAxMiklPiUKICBhZGRQcm92aWRlclRpbGVzKCJDYXJ0b0RCLlBvc2l0cm9uIikKYGBgCk1hcCBmb3IgUXVlZW5zIEJvcm91Z2g6CmBgYHtyfQpkZlEgPC0gZGZhVHJhaW5EdXAgJT4lIGZpbHRlcihib3JvdWdoPT0nUXVlZW5zJykKYGBgCgpgYGB7cn0KbGlicmFyeSgibGVhZmxldCIpCiNDcmVhdGluZyBMaXN0aW5ncyBhY3Jvc3MgTllDCmxlYWZsZXQoZHBseXI6OmJpbmRfcm93cyhkZlEsZGZ6aXBTKSkgJT4lCiBhZGRUaWxlcygpICU+JQogYWRkQ2lyY2xlTWFya2VycyhkYXRhPWRmUSwgY29sb3IgPSAnIzlENycsCiAgICBvcGFjaXR5ID0gMSx+bG9uZ2l0dWRlLCB+bGF0aXR1ZGUsbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IEYpLGNsdXN0ZXJPcHRpb25zID0gbWFya2VyQ2x1c3Rlck9wdGlvbnMoKSxwb3B1cCA9IHBhc3RlMCggIjxicj4gPGI+IFByaWNlOiA8L2I+IiwgZGZRJHByaWNlLCAiPGJyLz48Yj4gUm9vbSBUeXBlOiA8L2I+IiwgZGZRJHJvb21fdHlwZSwgIjxici8+PGI+IFByb3BlcnR5IFR5cGU6IDwvYj4iLCBkZlEkcHJvcGVydHlfdHlwZSwiPGJyPiA8Yj4gQW1lbml0aWVzOiA8L2I+IiwgZGZRJHRvdGFsX2FtZW5pdGllcywiPGJyPiA8Yj4gUmV2aWV3IFNjb3JlOiA8L2I+IiwgZGZRJHJldmlld19zY29yZXNfdmFsdWUsIjxicj4gPGI+IEFtZW5pdGllczogPC9iPiIsIGRmUSR0b3RhbF9hbWVuaXRpZXMsIjxicj4gPGI+IEJvb2tpbmcgUmF0ZTogPC9iPiIsIGRmUSRoaWdoX2Jvb2tpbmdfcmF0ZQogICAgICAgICAgICkpICU+JSAKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGE9ZGZ6aXBTLGNvbG9yID0gJyNGQTUnLG9wYWNpdHkgPSAxLHBvcHVwID0gcGFzdGUwKCAiPGJyPiA8Yj4gU3RhdGlvbiBOYW1lOiA8L2I+IiwgZGZ6aXBTJFN0YXRpb25uYW1lKSkgJT4lIAogIHNldFZpZXcoLTc0LjAwLCA0MC43MSwgem9vbSA9IDEyKQogIyU+JQogICNhZGRQcm92aWRlclRpbGVzKCJDYXJ0b0RCLlBvc2l0cm9uIikKYGBgCk1hcCBmb3IgQnJvbnggQm9yb3VnaDoKYGBge3J9CmRmQnIgPC0gZGZhVHJhaW5EdXAgJT4lIGZpbHRlcihib3JvdWdoPT0nQnJvbngnKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KCJsZWFmbGV0IikKI0NyZWF0aW5nIExpc3RpbmdzIGFjcm9zcyBOWUMKbGVhZmxldChkcGx5cjo6YmluZF9yb3dzKGRmQnIsZGZ6aXBTKSkgJT4lCiBhZGRUaWxlcygpICU+JQogYWRkQ2lyY2xlTWFya2VycyhkYXRhPWRmQnIsIGNvbG9yID0gJyM5RDcnLAogICAgb3BhY2l0eSA9IDEsfmxvbmdpdHVkZSwgfmxhdGl0dWRlLGxhYmVsT3B0aW9ucyA9IGxhYmVsT3B0aW9ucyhub0hpZGUgPSBGKSxjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkscG9wdXAgPSBwYXN0ZTAoICI8YnI+IDxiPiBQcmljZTogPC9iPiIsIGRmQnIkcHJpY2UsICI8YnIvPjxiPiBSb29tIFR5cGU6IDwvYj4iLCBkZkJyJHJvb21fdHlwZSwgIjxici8+PGI+IFByb3BlcnR5IFR5cGU6IDwvYj4iLCBkZkJyJHByb3BlcnR5X3R5cGUsIjxicj4gPGI+IEFtZW5pdGllczogPC9iPiIsIGRmQnIkdG90YWxfYW1lbml0aWVzLCI8YnI+IDxiPiBSZXZpZXcgU2NvcmU6IDwvYj4iLCBkZkJyJHJldmlld19zY29yZXNfdmFsdWUsIjxicj4gPGI+IEFtZW5pdGllczogPC9iPiIsIGRmQnIkdG90YWxfYW1lbml0aWVzLCI8YnI+IDxiPiBCb29raW5nIFJhdGU6IDwvYj4iLCBkZkJyJGhpZ2hfYm9va2luZ19yYXRlCiAgICAgICAgICAgKSkgJT4lIAogIGFkZENpcmNsZU1hcmtlcnMoZGF0YT1kZnppcFMsY29sb3IgPSAnI0ZBNScsb3BhY2l0eSA9IDEscG9wdXAgPSBwYXN0ZTAoICI8YnI+IDxiPiBTdGF0aW9uIE5hbWU6IDwvYj4iLCBkZnppcFMkU3RhdGlvbm5hbWUpKSAlPiUgCiAgc2V0VmlldygtNzQuMDAsIDQwLjcxLCB6b29tID0gMTIpCmBgYApNYXAgZm9yIFN0YXRlbiBJc2xhbmQgQm9yb3VnaDoKYGBge3J9CmRmUyA8LSBkZmFUcmFpbkR1cCAlPiUgZmlsdGVyKGJvcm91Z2g9PSdTdGF0ZW4gSXNsYW5kJykKYGBgCgpgYGB7cn0KbGlicmFyeSgibGVhZmxldCIpCiNDcmVhdGluZyBMaXN0aW5ncyBhY3Jvc3MgTllDCmxlYWZsZXQoZHBseXI6OmJpbmRfcm93cyhkZlMsZGZ6aXBTKSkgJT4lCiBhZGRUaWxlcygpICU+JQogYWRkQ2lyY2xlTWFya2VycyhkYXRhPWRmUywgY29sb3IgPSAnIzlENycsCiAgICBvcGFjaXR5ID0gMSx+bG9uZ2l0dWRlLCB+bGF0aXR1ZGUsbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IEYpLGNsdXN0ZXJPcHRpb25zID0gbWFya2VyQ2x1c3Rlck9wdGlvbnMoKSxwb3B1cCA9IHBhc3RlMCggIjxicj4gPGI+IFByaWNlOiA8L2I+IiwgZGZTJHByaWNlLCAiPGJyLz48Yj4gUm9vbSBUeXBlOiA8L2I+IiwgZGZTJHJvb21fdHlwZSwgIjxici8+PGI+IFByb3BlcnR5IFR5cGU6IDwvYj4iLCBkZlMkcHJvcGVydHlfdHlwZSwiPGJyPiA8Yj4gQW1lbml0aWVzOiA8L2I+IiwgZGZTJHRvdGFsX2FtZW5pdGllcywiPGJyPiA8Yj4gUmV2aWV3IFNjb3JlOiA8L2I+IiwgZGZTJHJldmlld19zY29yZXNfdmFsdWUsIjxicj4gPGI+IEFtZW5pdGllczogPC9iPiIsIGRmUyR0b3RhbF9hbWVuaXRpZXMsIjxicj4gPGI+IEJvb2tpbmcgUmF0ZTogPC9iPiIsIGRmUyRoaWdoX2Jvb2tpbmdfcmF0ZQogICAgICAgICAgICkpICU+JSAKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGE9ZGZ6aXBTLGNvbG9yID0gJyNGQTUnLG9wYWNpdHkgPSAxLHBvcHVwID0gcGFzdGUwKCAiPGJyPiA8Yj4gU3RhdGlvbiBOYW1lOiA8L2I+IiwgZGZ6aXBTJFN0YXRpb25uYW1lKSkgJT4lIAogIHNldFZpZXcoLTc0LjAwLCA0MC43MSwgem9vbSA9IDEyKQpgYGAKCgojIyMgMi4gRGF0YSBFeHBsb3JhdGlvbiBhbmQgVmlzdWFsaXphdGlvbgoKICAgIGEpIERpc3RyaWJ1dGlvbiBvZiBoaWdoIGFuZCBsb3cgYm9va2luZyByYXRlcyBhY3Jvc3MgYm9yb3VnaHMKCmBgYHtyfQpkZmFUcmFpbiAlPiUgCiAgZ3JvdXBfYnkoYm9yb3VnaCkgJT4lIAogIHN1bW1hcml6ZShIaWdoQm9va2luZ1llcyA9IGxlbmd0aChib3JvdWdoW2FzLmZhY3RvcihoaWdoX2Jvb2tpbmdfcmF0ZSkgPT0gMV0pLAogICAgICAgICAgICBIaWdoQm9va2luZ05vID0gbGVuZ3RoKGJvcm91Z2hbYXMuZmFjdG9yKGhpZ2hfYm9va2luZ19yYXRlKSA9PSAwXSkpICU+JQogIG11dGF0ZShIaWdoQm9va2luZ1llc1BjdCA9IEhpZ2hCb29raW5nWWVzKjEwMC8oSGlnaEJvb2tpbmdZZXMrSGlnaEJvb2tpbmdObyksCiAgICAgICAgIEhpZ2hCb29raW5nTm9QY3QgPSBIaWdoQm9va2luZ05vKjEwMC8oSGlnaEJvb2tpbmdZZXMrSGlnaEJvb2tpbmdObykpICU+JQogIGFycmFuZ2UoZGVzYyhIaWdoQm9va2luZ1llc1BjdCkpCmBgYApNYW5oYXR0YW4gYW5kIEJyb29rbHluIGhhdmUgdGhlIG1vc3QgbnVtYmVyIG9mIHByb3BlcnRpZXMuIEhvd2V2ZXIsIHRoaXMgZG9lc24ndCBuZWNlc3NhcmlseSB0cmFuc2xhdGUgaW50byBhIGdyZWF0ZXIgcGVyY2VudGFnZSBvZiBoaWdoIGJvb2tpbmcgcmF0ZXMuIFN0YXRlbiBJc2xhbmQgd2l0aCB0aGUgbGVhc3QgbnVtYmVyIG9mIHByb3BlcnRpZXMsIGhhcyBhIHJlbGF0aXZlbHkgZ3JlYXRlciBwZXJjZW50YWdlIG9mIGhpZ2ggYm9va2luZyByYXRlcy4KCmBgYHtyfQpwbG90Qm9yb3VnaEJvb2tpbmcgPC0gZ2dwbG90KGRhdGEgPSBkZmFUcmFpbiwgYWVzKHg9Ym9yb3VnaCwgZmlsbD1hcy5mYWN0b3IoaGlnaF9ib29raW5nX3JhdGUpKSkgKwogICAgICAgICAgICBnZW9tX2hpc3RvZ3JhbShjb2xvcj0nYmxhY2snLHN0YXQ9J2NvdW50JykgK3hsYWIoJ0Jvcm91Z2gnKSArIHlsYWIoJ051bWJlciBvZiBQcm9wZXJ0aWVzJykgK2dndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBib29raW5nIHJhdGVzIGJyb2tlbiBkb3duIGJ5IGJvcm91Z2hzIikKcGxvdEJvcm91Z2hCb29raW5nCmBgYApUaGUgcHJvcGVydGllcyB3aXRoIGhpZ2ggYm9va2luZyByYXRlcyBhcmUgbGVzcyB0aGFuIHRoZSBvbmVzIHdpdGggbG93IGJvb2tpbmcgcmF0ZXMuIFRoaXMgbWlnaHQgYmUgZHVlIHRvIGxlc3NlciBudW1iZXIgb2YgcHJvcGVydGllcyBoYXZpbmcgY2VydGFpbiBkZXNpcmVkIGFtZW5pdGllcyBhcyBjb21wYXJlZCB0byB0aGUgcHJvcGVydGllcyB3aXRoIGFsbCByZXF1aXJlZCBhbWVuaXRpZXMuCiAKCgogICAgYikgRGlzdHJpYnV0aW9uIG9mIHByaWNlcyBhY3Jvc3MgYm9yb3VnaHMKYGBge3J9CnBsb3RQcmljZUJvcm91Z2ggPC0gZ2dwbG90KGRhdGEgPSBkZmFUcmFpbiwgYWVzKHg9YXMuZmFjdG9yKGJvcm91Z2gpLCB5PXByaWNlKSkgKwogICAgICAgICAgICBnZW9tX2JveHBsb3QoKSArIHhsYWIoJ0Jvcm91Z2gnKSAreWxhYignUHJpY2UnKQpwbG90UHJpY2VCb3JvdWdoCmBgYAoKCmBgYHtyfQpwbG90UHJpY2VCb3JvdWdoMiA8LSBnZ3Bsb3QoZGF0YSA9IGRmYVRyYWluLCBhZXMoeD1hcy5mYWN0b3IoYm9yb3VnaCksIHk9cHJpY2UpKSArCiAgICAgICAgICAgIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlID0gTkEpICsgeGxhYignQm9yb3VnaCcpICt5bGFiKCdQcmljZScpICsgeWxpbSgwLDUwMCkKcGxvdFByaWNlQm9yb3VnaDIKYGBgClByaWNlcyBzZWVtIHRvIGJlIGhpZ2hlciBhbmQgaGF2ZSBhIHdpZGVyIGRpc3RyaWJ1dGlvbiBpbiBNYW5oYXR0YW4gYW5kIEJyb29rbHluLiBUaGlzIG1pZ2h0IGFsc28gYmUgY29udHJpYnV0aW5nIHRvIGEgcmVsYXRpdmVseSBsb3dlciBwZXJjZW50YWdlIG9mIGhpZ2ggYm9va2luZyByYXRlcyBpbiB0aGVzZSBib3JvdWdocy4KCgpJcyB0aGVyZSBtdWx0aWNvbGxpbmVhcml0eSBiZXR3ZWVuIHByaWNlIGFuZCBsb2NhdGlvbj8KYGBge3J9CmNhcjo6dmlmKGxtKGhpZ2hfYm9va2luZ19yYXRlIH4gYm9yb3VnaCtwcmljZSxkYXRhPWRmYVRyYWluKSkKYGBgClByaWNlIG1vdmVzIGluZGVwZW5kZW50bHkgb2YgbG9jYXRpb24uIFRoZXJlIGRvZXNuJ3Qgc2VlbSB0byBiZSBkaXJlY3QgY29ycmVsYXRpb24gYmV0d2VlbiBsb2NhdGlvbiBhbmQgcHJpY2UuCgogICAgYykgSW1wYWN0IG9mIHJldmlldyBzY29yZXMgb24gYm9va2luZyByYXRlCgpgYGB7cn0KI1JldmlldyBTY29yZXMKc3VtbWFyeShsbShoaWdoX2Jvb2tpbmdfcmF0ZX5yZXZpZXdfc2NvcmVzX2FjY3VyYWN5K3Jldmlld19zY29yZXNfY2hlY2tpbityZXZpZXdfc2NvcmVzX2NsZWFubGluZXNzK3Jldmlld19zY29yZXNfY29tbXVuaWNhdGlvbityZXZpZXdfc2NvcmVzX2xvY2F0aW9uK3Jldmlld19zY29yZXNfcmF0aW5nK3Jldmlld19zY29yZXNfdmFsdWUsZGF0YT1kZmFUcmFpbikpCmBgYApFeGNlcHQgZm9yIHJldmlld19zY29yZV92YWx1ZSwgYWxsIG90aGVyIHJldmlldyBzY29yZXMgc2VlbSB0byBiZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LgpJcyB0aGVyZSBtdWx0aWNvbGxpbmVhcml0eSBiZXR3ZWVuIHRoZSByZXZpZXcgc2NvcmVzID8KYGBge3J9CmNhcjo6IHZpZihsbShoaWdoX2Jvb2tpbmdfcmF0ZX5yZXZpZXdfc2NvcmVzX2FjY3VyYWN5K3Jldmlld19zY29yZXNfY2hlY2tpbityZXZpZXdfc2NvcmVzX2NsZWFubGluZXNzK3Jldmlld19zY29yZXNfY29tbXVuaWNhdGlvbityZXZpZXdfc2NvcmVzX2xvY2F0aW9uK3Jldmlld19zY29yZXNfcmF0aW5nK3Jldmlld19zY29yZXNfdmFsdWUsZGF0YT1kZmFUcmFpbikpCmBgYApZZXMsIHRoZXJlIHNlZW0gdG8gYmUgbXVsdGljb2xsaW5lYXJpdHkgYmV0d2VlbiB0aGUgcmV2aWV3IHNjb3Jlcy4gUG9zc2libHksIHJldmlldyBzY29yZSByYXRpbmcgaXMgdGhlIG92ZXJhbGwgcmF0aW5nIGFuZCBoZW5jZSBjb3VsZCBiZSB0aGUgcmVhc29uIGZvciB0aGUgbXVsdGljb2xsaW5lYXJpdHkuCgpgYGB7cn0KY2FyOjogdmlmKGxtKGhpZ2hfYm9va2luZ19yYXRlfnJldmlld19zY29yZXNfYWNjdXJhY3krcmV2aWV3X3Njb3Jlc19jaGVja2luK3Jldmlld19zY29yZXNfY2xlYW5saW5lc3MrcmV2aWV3X3Njb3Jlc19jb21tdW5pY2F0aW9uK3Jldmlld19zY29yZXNfbG9jYXRpb24rcmV2aWV3X3Njb3Jlc192YWx1ZSxkYXRhPWRmYVRyYWluKSkKYGBgCmBgYHtyfQpjYXI6OiB2aWYobG0oaGlnaF9ib29raW5nX3JhdGV+cmV2aWV3X3Njb3Jlc19jaGVja2luK3Jldmlld19zY29yZXNfY2xlYW5saW5lc3MrcmV2aWV3X3Njb3Jlc19jb21tdW5pY2F0aW9uK3Jldmlld19zY29yZXNfbG9jYXRpb24rcmV2aWV3X3Njb3Jlc192YWx1ZSxkYXRhPWRmYVRyYWluKSkKYGBgCkFsc28sIHJldmlld19zY29yZXNfYWNjdXJhY3kgY2FwdHVyZXMgaW5mb3JtYXRpb24gYWJvdXQgaG93IGFjY3VyYXRlbHkgdGhlIHNwYWNlIHdhcyByZXByZXNlbnRlZCBieSB0aGUgbGlzdGluZy4gQSBwYXJ0IG9mIHRoaXMgaW5mb3JtYXRpb24gY291bGQgYWxzbyBiZSBjYXB0dXJlZCBieSB0aGUgb3RoZXIgcmV2aWV3X3Njb3Jlcy4gSGVuY2UsIHdlIGNvdWxkIHJlbW92ZSByZXZpZXdfc2NvcmVfYWNjdXJhY3kgYXMgd2VsbC4gCmBgYHtyfQpzdW1tYXJ5KGxtKGhpZ2hfYm9va2luZ19yYXRlfnJldmlld19zY29yZXNfY2hlY2tpbityZXZpZXdfc2NvcmVzX2NsZWFubGluZXNzK3Jldmlld19zY29yZXNfY29tbXVuaWNhdGlvbityZXZpZXdfc2NvcmVzX2xvY2F0aW9uK3Jldmlld19zY29yZXNfdmFsdWUsZGF0YT1kZmFUcmFpbikpCmBgYAoKICAgIGQpIERpc3RyaWJ1dGlvbiBvZiBwcm9wZXJ0eSB0eXBlcyBhY3Jvc3MgYm9yb3VnaHMKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKcHJvcGVydHlkZiA8LSAgZGZhVHJhaW5EdXAgJT4lIGdyb3VwX2J5KGJvcm91Z2gsIHByb3BlcnR5X3R5cGUpICU+JSBzdW1tYXJpemUoRnJlcSA9IG4oKSkKcHJvcGVydHlkZiA8LSBwcm9wZXJ0eWRmICU+JSBmaWx0ZXIocHJvcGVydHlfdHlwZSAlaW4lIGMoIkFwYXJ0bWVudCIsIkhvdXNlIiwiQ29uZG9taW5pdW0iLCJUb3duaG91c2UiLCAiTG9mdCIsIkd1ZXN0IHN1aXRlIikpCnRvdGFscHJvcGVydHk8LSAgZGZhVHJhaW5EdXAgJT4lIGZpbHRlcihwcm9wZXJ0eV90eXBlICVpbiUgYygiQXBhcnRtZW50IiwiSG91c2UiLCJDb25kb21pbml1bSIsIlRvd25ob3VzZSIsICJMb2Z0IiwiR3Vlc3Qgc3VpdGUiKSklPiUgZ3JvdXBfYnkoYm9yb3VnaCkgJT4lIHN1bW1hcml6ZShzdW0gPSBuKCkpCgpwcm9wZXJ0eXJhdGlvIDwtIG1lcmdlKHByb3BlcnR5ZGYsIHRvdGFscHJvcGVydHksIGJ5PSJib3JvdWdoIikKcHJvcGVydHlyYXRpbyA8LSBwcm9wZXJ0eXJhdGlvICU+JSBtdXRhdGUocmF0aW8gPSBGcmVxL3N1bSkKZ2dwbG90KHByb3BlcnR5cmF0aW8sIGFlcyh4PWJvcm91Z2gsIHk9cmF0aW8sIGZpbGwgPSBwcm9wZXJ0eV90eXBlKSkgKwogIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIixzdGF0PSJpZGVudGl0eSIpICsgeGxhYigiQm9yb3VnaCIpICsgeWxhYigiQ291bnQiKSsKICBzY2FsZV9maWxsX2Rpc2NyZXRlKG5hbWUgPSAiUHJvcGVydHkgVHlwZSIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogIGdndGl0bGUoIlByb3BlcnR5IFR5cGVzIGluIE5ZQyIsCiAgICAgICAgICBzdWJ0aXRsZSA9ICJNYXAgc2hvd2luZyBQZXJjZW50YWdlIENvdW50IG9mIFByb3BlcnR5IFR5cGUgYnkgQm9yb3VnaCAiKSArCiAgICAgICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSArCiAgICAgICAgICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGNvbG9yID0gImdyZXkzNSIpKSArCiAgICAgICAgICB0aGVtZShwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoY29sb3IgPSAiZ3JleTY4IikpK3NjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0iI2QzY2JjYiIsIGhpZ2g9IiM4NTJlYWEiKSsKICAgICAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKCJQcm9wZXJ0eSBUeXBlIiwgdmFsdWVzPWMoIiNlMDZmNjkiLCIjMzU3YjhhIiwgIiM3ZGI1YjgiLCAiIzU5YzZmMyIsICIjZjZjNDU4IiwiIzAwRkYwMCIpKSArCiAgICAgICAgICB4bGFiKCJOZWlnaGJvcmhvb2QiKSArIHlsYWIoIlBlcmNlbnRhZ2UiKQpgYGAKQ29uc2lkZXJpbmcgdGhlIG1vc3QgcHJlZmVycmVkIHByb3BlcnR5IHR5cGVzIGluIGFsbCB0aGUgYm9yb3VnaHMgcGxvdHRlZCBhYm92ZSx3ZSBjYW4gc2VlIHRoYXQgQXBhcnRtZW50IGlzIHRoZSBtb3N0IHByZWZlcnJlZCBQcm9wZXJ0eSB0eXBlIGluIEJyb254LCBCcm9va2x5biwgTWFuaGF0dGFuIGFuZCBRdWVlbnMuVGhpcyBjb3VsZCBiZSBtYWlubHkgZHVlIHRvIHRoZSBwZW9wbGUgdmlzaXRpbmcgdGhlc2UgYm9yb3VnaHMgZm9yIHNpZ2h0IHNlZWluZyBvciBidXNpbmVzcyB0cmlwcy4gT24gdGhlIG90aGVyIGhhbmQsIEhvdXNlcyBhcmUgbW9zdCBwcmVmZXJyZWQgaW4gU3RhdGVuIElzbGFuZCBhcyB0aGV5IGFyZSBsZXNzIGV4cGVuc2l2ZSB0aGFuIHRoZSBob3VzZXMgaW4gb3RoZXIgYm9yb3VnaHMgYW5kIHBlb3BsZSB2aXNpdCBpdCBtb3N0bHkgZm9yIGxlaXN1cmUuCgogICAgZSkgUm9vbSBUeXBlIEFuYWx5c2lzCkRpc3RyYnV0aW9uIG9mIHByb3BlcnRpZXMgaW4gTllDIGJhc2VkIG9uIHJvb20gdHlwZSBhbG9uZyB3aXRoIHRoZSBjb3VudCBvZiB0aGUgaGlnaCBib29raW5nIHJhdGUgcHJvcGVydGllcyBhbmQgaXQncyBwZXJjZW50YWdlCmBgYHtyfQpkZnIxIDwtIGRmYVRyYWluICU+JSBncm91cF9ieShyb29tX3R5cGUpICU+JSBzdW1tYXJpc2UoY291bnRfaGlnaGJvb2tpbmc9c3VtKGhpZ2hfYm9va2luZ19yYXRlKSx0b3RhbD1sZW5ndGgoaGlnaF9ib29raW5nX3JhdGUpKSU+JQogIG11dGF0ZShQY3QgPSBjb3VudF9oaWdoYm9va2luZyoxMDAvdG90YWwpICU+JSBhcnJhbmdlKGRlc2MoUGN0KSkKZGZyMQpgYGAKRGlzdHJpYnV0aW9uIG9mIFByb3BlcnRpZXMgYmFzZWQgb24gUm9vbSBUeXBlCmBgYHtyfQpwbG90MSA8LSBnZ3Bsb3QoZGF0YSA9IGRmcjEsIGFlcyh4PXJvb21fdHlwZSwgeT10b3RhbCxmaWxsPXJvb21fdHlwZSkpICsKICAgICAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoPTAuNSxjb2xvdXI9ImJsYWNrIikrbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgUHJvcGVydGllcyBiYXNlZCBvbiBSb29tIFR5cGUiLHg9IlJvb20gVHlwZSIseT0iTm8gb2YgUHJvcGVydGllcyIpCnBsb3QxCmBgYApQZXJjZW50YWdlIG9mIFByb3BlcnRpZXMgd2l0aCBoaWdoIGJvb2tpbmcgcmF0ZSBiYXNlZCBvbiBSb29tIFR5cGUKYGBge3J9CnBsb3QyIDwtIGdncGxvdChkYXRhID0gZGZyMSwgYWVzKHg9cm9vbV90eXBlLCB5PVBjdCxmaWxsPXJvb21fdHlwZSkpICsKICAgICAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoPTAuNSxjb2xvdXI9ImJsYWNrIikrbGFicyh0aXRsZSA9ICJQZXJjZW50YWdlIG9mIFByb3BlcnRpZXMgd2l0aCBoaWdoIGJvb2tpbmcgcmF0ZSBiYXNlZCBvbiBSb29tIFR5cGUiLHg9IlJvb20gVHlwZSIseT0iUGVyY2VudGFnZSBvZiBQcm9wZXJ0aWVzIikKcGxvdDIKYGBgCkRpc3RyaWJ1dGlvbiBvZiBwcm9wZXJ0aWVzIHdpdGggaGlnaCBib29raW5nIHJhdGUgaW4gZWFjaCBib3JvdWdoCmBgYHtyfQpkZnI0IDwtIGRmYVRyYWluICU+JSBmaWx0ZXIoaGlnaF9ib29raW5nX3JhdGU9PTEpICU+JSBncm91cF9ieShib3JvdWdoLHJvb21fdHlwZSklPiUgCiAgc3VtbWFyaXNlKHRvdGFsX2hpZ2hfYm9va2luZ19yYXRlPWxlbmd0aChoaWdoX2Jvb2tpbmdfcmF0ZSkpCgpwbG90MyA8LSBnZ3Bsb3QoZGF0YSA9IGRmcjQsIGFlcyh4PWJvcm91Z2gsIHk9dG90YWxfaGlnaF9ib29raW5nX3JhdGUsZmlsbD1yb29tX3R5cGUpKSArCiAgICAgICAgICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uPSJzdGFjayIsY29sb3VyPSJibGFjayIpK2xhYnModGl0bGU9IkRpc3RyaWJ1dGlvbiBvZiBwcm9wZXJ0aWVzIHdpdGggaGlnaCBib29raW5nIHJhdGUgaW4gZWFjaCBib3JvdWdoIix4PSJCb3JvdWdoIix5PSJDb3VudCBvZiBoaWdoIGJvb2tpbmcgcmF0ZSBwcm9wZXJ0aWVzIikKcGxvdDMKCmBgYAoKRnJvbSB0aGUgYWJvdmUgdmlzdWFsaXNhdGlvbnMsIHdlIG9ic2VydmUgdGhhdCBFbnRpcmUgSG9tZS9BcGFydG1lbnQgYW5kIFByaXZhdGUgcm9vbXMgYXJlIHRoZSBtb3N0IHBvcHVsYXIgcm9vbSB0eXBlcyBvZmZlcmVkIGluIE5ZQyBhbmQgYWxzbyB0aGUgbW9zdCBzdWNjZXNzZnVsIG9uZXMgaW4gdGVybXMgb2YgaGlnaCBib29raW5nIHJhdGUuCgoKSWYgd2UgdHJ5IGdvaW5nIG5laWdoYm91cmhvb2Qgd2lzZSwKCmBgYHtyfQpkZmFUcmFpbiAlPiUgZ3JvdXBfYnkocm9vbV90eXBlLG5laWdoYm91cmhvb2QpICU+JSBzdW1tYXJpc2UoY291bnRfaGlnaGJvb2tpbmc9c3VtKGhpZ2hfYm9va2luZ19yYXRlKSx0b3RhbD1sZW5ndGgoaGlnaF9ib29raW5nX3JhdGUpKSU+JQogIG11dGF0ZShQY3QgPSBjb3VudF9oaWdoYm9va2luZyoxMDAvdG90YWwpICU+JSBhcnJhbmdlKGRlc2MoUGN0KSkgJT4lIGZpbHRlcih0b3RhbD4xMCkKYGBgCk5vIHN1Y2ggY2xlYXIgaW5mZXJlbmNlIGlmIHdlIGdvIG5laWdoYm91cmhvb2Qgd2lzZS4KCkRpc3RyaWJ1dGlvbiBvZiBjb3VudCBvZiBoaWdoIGJvb2tpbmcgcHJvcGVydGllcyBpbiBlYWNoIGJvcm91Z2ggYW5kIHRoZSB0b3RhbCBwcm9wZXJ0aWVzIGJhc2VkIG9uIGRpZmZlcmVudCByb29tIHR5cGVzIGF2YWlsYWJsZSAoUm9vbSB0eXBlcyBoYXZpbmcgZ3JlYXRlciB0aGFuIDEwIG9ic2VydmF0aW9ucykKYGBge3J9CmRmcjIgPC0gZGZhVHJhaW4gJT4lIGdyb3VwX2J5KHJvb21fdHlwZSxib3JvdWdoKSAlPiUgc3VtbWFyaXNlKGNvdW50X2hpZ2hib29raW5nPXN1bShoaWdoX2Jvb2tpbmdfcmF0ZSksdG90YWxfcHJvcGVydGllcz1sZW5ndGgoaGlnaF9ib29raW5nX3JhdGUpKSU+JQogIG11dGF0ZShQZXJjZW50YWdlX2hpZ2hib29raW5nID0gY291bnRfaGlnaGJvb2tpbmcqMTAwL3RvdGFsX3Byb3BlcnRpZXMpICU+JSBhcnJhbmdlKGRlc2MoUGVyY2VudGFnZV9oaWdoYm9va2luZykpICU+JSBmaWx0ZXIodG90YWxfcHJvcGVydGllcz4xMCkKZGZyMgoKYGBgCllvdSBjYW4gc2VlIHRoYXQgRW50aXJlIGhvbWUvYXB0IGFuZCBQcml2YXRlIHJvb20gc3RhbmRvdXQgYXMgdGhlIG1vc3Qgc3VjY2Vzc2Z1bCBvbmVzIGhlcmUgd2hlbiB3ZSBjaGVjayBldmVyeSBib3JvdWdoCgpEaWZmZXJlbnQgcm9vbXR5cGVzIGF2YWlsYWJsZSBpbiBlYWNoIGJvcm91Z2ggYWxvbmcgd2l0aCB0aGVpciBzdWNjZXNzIHJhdGUgaW4gYW4gYXNjZW5kaW5nIG9yZGVyCmBgYHtyfQogZGZyMyA8LSBkZmFUcmFpbiAlPiUgZ3JvdXBfYnkoYm9yb3VnaCxyb29tX3R5cGUKKSAlPiUgc3VtbWFyaXNlKGNvdW50X2hpZ2hib29raW5nPXN1bShoaWdoX2Jvb2tpbmdfcmF0ZSksdG90YWxfcHJvcGVydGllcz1sZW5ndGgoaGlnaF9ib29raW5nX3JhdGUpKSU+JQogIG11dGF0ZShQZXJjZW50YWdlX2hpZ2hib29raW5nID0gY291bnRfaGlnaGJvb2tpbmcqMTAwL3RvdGFsX3Byb3BlcnRpZXMpJT4lIGZpbHRlcihjb3VudF9oaWdoYm9va2luZz41KSAlPiUgYXJyYW5nZShQZXJjZW50YWdlX2hpZ2hib29raW5nKQpkZnIzCgpgYGAKCgpTaGFyZWQgcm9vbSBpbiBRdWVlbnMgb3IgQnJvb2x5biBpcyBhIGJhZCBpZGVhIGJ1dCBzaGFyZWQgaW4gTWFuaGF0dGFuIG1heSB3b3JrIGJlY2F1c2Ugb2YgaG93IGV4cGVuc2l2ZSB0aGUgYXJlYSBpcy5Ib3RlbCBSb29tcyBpbiBNYW5oYXR0YW4gd291bGQgbm90IGJlIGEgZ29vZCBpZGVhLkVudGlyZSBob21lL2FwdCBhbmQgUHJpdmF0ZSByb29tcyBzZWVtIHRvIGJlIGhhdmluZyB0aGUgYmVzdCBQZXJjZW50YWdlIG91dHB1dCBidXQgYWxsIHN0aWxsIGluIHRoZSByYW5nZSBvZiAxNSUtMzUlCgpNb3N0IHBvcHVsYXIgcm9vbSB0eXBlcyBwZXIgYm9yb3VnaCByYW5rIHdpc2UgYW5kIHZpc3VhbGlzYXRpb24gb2YgdGhlIHN1Y2Nlc3NmdWwgbGlzdGluZ3MgYnJva2VuIGRvd24gYnkgUm9vbSB0eXBlcyBpbiBlYWNoIGJvcm91Z2gKYGBge3J9CiBkZnI1IDwtIGRmYVRyYWluICU+JSBncm91cF9ieShib3JvdWdoLHJvb21fdHlwZQopICU+JSBzdW1tYXJpc2UodG90YWw9bGVuZ3RoKGhpZ2hfYm9va2luZ19yYXRlKSkgJT4lIGFycmFuZ2UoYm9yb3VnaCwgZGVzYyh0b3RhbCkpICU+JSBtdXRhdGUocmFuaz1yYW5rKC10b3RhbCkpCmRmcjUKYGBgClZpc3VhbGlzYXRpb24gb2YgdGhlIHN1Y2Nlc3NmdWwgbGlzdGluZ3MgYnJva2VuIGRvd24gYnkgcm9vbSB0eXBlIGluIGVhY2ggYm9yb3VnaApgYGB7cn0KCnBsb3Q0IDwtIGdncGxvdChkYXRhID0gZGZyNSwgYWVzKHg9Ym9yb3VnaCwgeT10b3RhbCxmaWxsPXJvb21fdHlwZSkpICsKICAgICAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIscG9zaXRpb249ImRvZGdlIixjb2xvcj0nYmxhY2snKStsYWJzKHg9IkJvcm91Z2giLHk9IkNvdW50IG9mIHN1Y2Nlc3NmdWwgbGlzdGluZ3MiLHRpdGxlPSJWaXN1YWxpc2F0aW9uIG9mIHRoZSBzdWNjZXNzZnVsIGxpc3RpbmdzIGJyb2tlbiBkb3duIGJ5IHJvb20gdHlwZSBpbiBlYWNoIGJvcm91Z2giKQpwbG90NApgYGAKRW50aXJlIGhvbWUvYXB0IGFuZCBQcml2YXRlIHJvb21zIHNlZW0gdG8gYmUgaGF2aW5nIHRoZSBoaWdoZXN0IGNvdW50IG9mIHN1Y2Nlc3NmdWwgbGlzdGluZ3MgYW5kIGFyZSBhbHNvIHRoZSBtb3N0IHBvcHVsYXIgb25lcyBpbiBtb3N0IG9mIHRoZSBib3JvdWdocy5UaGlzIGNvdWxkIGFsc28gdGVsbCB1cyB0aGF0IGJlY2F1c2UgdGhleSBkbyBnb29kIGJvb2tpbmcgcmF0ZSB3aXNlIHRoZXkgYXJlIHRoZSBtb3N0IHBvcHVsYXIgb3IgdmljZSB2ZXJzYS4gTmV2ZXJ0aGVsZXNzLCBFbnRpcmUgaG9tZS9hcHQgYW5kIFByaXZhdGUgcm9vbSBzZWVtIHRvIGJlIHRoZSBsZWFzdCByaXNrIHRha2luZyBvcHRpb24gaW4gTllDLgoKU29tZSBndWVzdHMgYXJlIHNvY2lhbCBidXR0ZXJmbGllcyBhbmQgdXNlIEFpcmJuYiB0byBtZWV0IG5ldyBwZW9wbGUgb24gdGhlaXIgdHJhdmVscy4gT3RoZXIgZ3Vlc3RzIHByZWZlciB0byBiZSBwcml2YXRlIGFuZCBrZWVwIHRvIHRoZW1zZWx2ZXMuQ2hvb3NpbmcgYW4gQWlyYm5iIGVudGlyZSBob21lIG1lYW5zIHlvdSBnZXQgdGhlIHdob2xlIHBsYWNlIHRvIHlvdXJzZWxmLiBUaGVyZSBpcyBubyBzaGFyaW5nIHdpdGggaG9zdHMgb3Igb3RoZXIgZ3Vlc3RzLCBpdOKAmXMganVzdCB5b3UgYW5kIHlvdXIgcGFydHkuIFRoZSBBaXJibmIgaG9zdHMgZG8gbm90IHN0YXkgd2l0aCB5b3Ugd2hlbiB5b3UgcmVzZXJ2ZSBhbiBlbnRpcmUgaG9tZS4gCgpXaHkgYSB0cmF2ZWxsZXIgd291bGQgcHJlZmVyIGFuIGVudGlyZSBob21lL2FwYXJ0bWVudD8KCiAgICAxLiBObyBob3N0cyB3YXRjaGluZyB5b3VyIGV2ZXJ5IG1vdmUuCiAgICAyLiBUaGVyZeKAmXMgbm8gaW50ZXJhY3Rpb24uCiAgICAzLiBBbiBlbnRpcmUgcGxhY2UgaXMgZ3JlYXQgd2hlbiB5b3UganVzdCB3YW50IHNvbWV3aGVyZSB0byByZWxheC4gSWYgeW914oCZdmUgYmVlbiBidXN5IGFsbCBkYXksIHNvbWV0aW1lcyB0aGUgbGFzdCB0aGluZyB5b3Ugd2FudCB0byBkbyBpcyBjb21lIGJhY2sgYW5kIGhhdmUgdG8gYmUgc29jaWFsLCB0YWxraW5nIHRvIHN0cmFuZ2Vycy4KCldoeSBhIHRyYXZlbGxlciB3b3VsZCBwcmVmZXIgYSBQcml2YXRlIHJvb20/CgogICAgMS4gVGhleSBhcmUgc3VwZXIgY2hlYXAuCiAgICAyLiBGb3IgcGVvcGxlIHdobyBlbmpveSBpbnRlcmFjdGluZyB3aXRoIG90aGVycyB3aGlsZSBhbHNvIGhhdmluZyBzb21lIHNvcnQgb2YgcHJpdmFjeS4KICAgIDMuIFlvdXIgaG9zdCBrbm93cyB0aGUgYXJlYSBhbmQgaGFzIGdyZWF0IGFkdmljZSBvZiB0aGluZ3MgdG8gc2VlLgoKU28gZGVwZW5kaW5nIG9uIHdoaWNoIG1hcmtldCB0aGUgaW52ZXN0b3Igd2FudHMgdG8gdGFyZ2V0IGhlIGNhbiBvcHQgZm9yIGVpdGhlciBvZiB0aGUgYWJvdmUgb3B0aW9ucyBhbmQgY3JlYXRlIGEgZ3JlYXQgbGlzdGluZy4KCkZyb20gdGhlIGFib3ZlIGFuYWx5c2lzIHdlIGluZmVyIHRoYXQgdGhlIGludmVzdG9yIHNob3VsZCBlaXRoZXIgZ28gd2l0aCBsZWFzaW5nIHRoZSBwcm9wZXJ0eSBhcyBhbiBFbnRpcmUgSG9tZS9BcGFydG1lbnQgb3IgYSBQcml2YXRlIHJvb20gaW4gb3JkZXIgdG8gZ2V0IGEgYmV0dGVyIGNoYW5jZSBvZiBnZXR0aW5nIGEgaGlnaGVyIGJvb2tpbmcgcmF0ZQoKCiAgICBmKSBTdXBlciBIb3N0CgpBaXJibmIgYXdhcmRzIHRoZSB0aXRsZSBvZiDigJxTdXBlcmhvc3TigJ0gdG8gYSBzbWFsbCBmcmFjdGlvbiBvZiBpdHMgZGVwZW5kYWJsZSBob3N0cy4gVGhpcyBwcm9ncmFtIGlzIGFuIGluY2VudGl2ZSBwcm9ncmFtIHRoYXQgaXMgYSB3aW4td2luIGZvciBib3RoIHRoZSBob3N0LCBBaXJibmIsIGFuZCB0aGVpciBjdXN0b21lcnMuIFRoZSBzdXBlcmhvc3QgZ2FpbnMgbW9yZSBidXNpbmVzcyBpbiB0aGUgZm9ybSBvZiBoaWdoZXIgYm9va2luZ3MsIHRoZSBjdXN0b21lciByZWNlaXZlcyBpbXByb3ZlZCBzZXJ2aWNlIGFuZCBBaXJibmIgcHJvZml0cyB3aXRoIGhhcHB5IHNhdGlzZmllZCBjdXN0b21lcnMuCgpBaXJibmLigJlzIHNpdGUgcmVxdWlyZXMgYSBob3N0IHRvIHNhdGlzZnkgY2VydGFpbiByZXF1aXJlbWVudHMgdG8gYmUgY29uc2lkZXJlZCBhcyBhIHN1cGVyIGhvc3QuIFdlIGNvbnNpZGVyIHR3byBwYXJhbWV0ZXJzIGZyb20gdGhlIGRhdGFzZXQgYW5kIG1lYXN1cmUgdGhlIHBlcmZvcm1hbmNlOiDigJxSZXNwb25zZSByYXRl4oCdIGFuZCDigJxSYXRpbmdz4oCdIHdoaWNoIHJhbmdlIGZyb20gMCB0byAxMDAuCgpUaGUgc2NhdHRlciBwbG90IGdpdmVzIGEgZmV3IGludGVyZXN0aW5nIGluc2lnaHRzLiBXaGlsZSBtb3N0IHN1cGVyLWhvc3RzIGFyZSBpbiB0aGUgcmVnaW9uIG9mIGhpZ2gtcmF0aW5nOmhpZ2gtcmVzcG9uc2UtcmF0ZSByZWdpb24sIHdlIGNhbiBhbHNvIHNlZSBhIGZldyBob3N0cyB3aXRoIHJlc3BvbnNlIHJhdGVzIGxlc3MgdGhhbiA3NSUgd2hpY2ggdmlvbGF0ZXMgdGhlIDkwJSsgY3JpdGVyYSBzZXQgYnkgQWlyYm5iIGFsdGhvdWdoIHRoZXJlIGFyZSBhIHZlcnkgc21hbGwgZnJhY3Rpb24gb2YgdGhlIGhvc3RzLiBJbiByZWdhcmQgdG8gUmF0aW5ncywgYWxtb3N0IGFsbCBob3N0cyBhcmUgcmF0ZWQgNzUlIGFuZCBhYm92ZSB3aXRoIGEgdmVyeSBmZXcgYmVsb3cgNzUlLgpNb3N0IEFpcmJuYiBob3N0cyBuZWVkIHRvIGxpZSBpbiB0aGUgcmVnaW9uIG9mIGhpZ2gtcmF0aW5nOmhpZ2gtcmVzcG9uc2UgcmVnaW9uIHRvIGJlIGNvbnNpZGVyZWQgYSBzdXBlciBob3N0LCBidXQgb25seSBhIHNtYWxsIGZyYWN0aW9uIGNhbiBiZSBzdXBlciBob3N0cyBpZiB0aGUgY29uZGl0aW9ucyBhcmUgbm90IHNhdGlzZmllZC4KCgoKSG9zdCBSZXNwb25zZSBUaW1lCgpWaXN1YWxpc2F0aW9uIG9mIHRoZSBzdWNjZXNzZnVsIGxpc3RpbmdzIGJyb2tlbiBkb3duIGJ5IHRoZSBob3N0IHJlc3BvbnNlIHRpbWUKCmBgYHtyfQpkZmFUcmFpbiRob3N0X3Jlc3BvbnNlX3RpbWU8LSBkZmFUcmFpbiRob3N0X3Jlc3BvbnNlX3RpbWUlPiUgcmVwbGFjZV9uYSgiTi9BIikKcGxvdDUgPC0gZ2dwbG90KGRhdGEgPSBkZmFUcmFpbiAlPiUgZmlsdGVyKGhpZ2hfYm9va2luZ19yYXRlPT0xKSwgYWVzKHg9aG9zdF9yZXNwb25zZV90aW1lLGZpbGw9aG9zdF9yZXNwb25zZV90aW1lKSkgKwogICAgICAgICAgICBnZW9tX2hpc3RvZ3JhbShzdGF0ID0gImNvdW50Iixjb2xvcj0nYmxhY2snKStsYWJzKHg9IlJlc3BvbnNlIFRpbWUiLHk9IkNvdW50IG9mIGxpc3RpbmdzIHdpdGggaGlnaCBib29raW5nIHJhdGUgIix0aXRsZT0iVmlzdWFsaXNhdGlvbiBvZiB0aGUgc3VjY2Vzc2Z1bCBsaXN0aW5ncyBicm9rZW4gZG93biBieSB0aGUgaG9zdCByZXNwb25zZSB0aW1lIikKcGxvdDUKYGBgCgpMaXN0aW5ncyBoYXZpbmcgaG9zdHMgd2hvc2UgcmVzcG9uc2UgcmF0ZSBpcyBxdWljaywgaGF2ZSBhIGJldHRlciBib29raW5nIHJhdGUuIFRoaXMgY291bGQgYmUgYW5vdGhlciBmYWN0b3IgdGhhdCBzaG91bGQgYmUgY29uc2lkZXJlZCBhZnRlciB0aGUgaW52ZXN0b3IgaGFzIHB1cmNoYXNlZCB0aGUgcHJvcGVydHkuIAoKQWlyYm5iIGF3YXJkcyB0aGUgdGl0bGUgb2Yg4oCcU3VwZXJob3N04oCdIHRvIGEgc21hbGwgZnJhY3Rpb24gb2YgaXRzIGRlcGVuZGFibGUgaG9zdHMuIFRoaXMgcHJvZ3JhbSBpcyBhbiBpbmNlbnRpdmUgcHJvZ3JhbSB0aGF0IGlzIGEgd2luLXdpbiBmb3IgYm90aCB0aGUgaG9zdCwgQWlyYm5iLCBhbmQgdGhlaXIgY3VzdG9tZXJzLiBUaGUgc3VwZXJob3N0IGdhaW5zIG1vcmUgYnVzaW5lc3MgaW4gdGhlIGZvcm0gb2YgaGlnaGVyIGJvb2tpbmdzLCB0aGUgY3VzdG9tZXIgcmVjZWl2ZXMgaW1wcm92ZWQgc2VydmljZSBhbmQgQWlyYm5iIHByb2ZpdHMgd2l0aCBoYXBweSBzYXRpc2ZpZWQgY3VzdG9tZXJzLgoKCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFIH0KZGZhVHJhaW5TIDwtIGRmYVRyYWluRHVwCmRmYVRyYWluUyRob3N0X3Jlc3BvbnNlX3JhdGUgPSBhcy5pbnRlZ2VyKGdzdWIoIiUiLCIiLGRmYVRyYWluUyRob3N0X3Jlc3BvbnNlX3JhdGUpKQpkZmFUcmFpblMkcmV2aWV3X3Njb3Jlc19yYXRpbmcgPSBhcy5pbnRlZ2VyKGdzdWIoIiUiLCIiLGRmYVRyYWluUyRyZXZpZXdfc2NvcmVzX3JhdGluZykpCiNsaXN0MSA9IGxpc3RpbmdzWyFpcy5uYShsaXN0aW5ncyRob3N0X3Jlc3BvbnNlX3JhdGUpICYgaXMubmEobGlzdGluZ3MkcmV2aWV3X3Njb3Jlc19yYXRpbmcpLF0KbDEgPSBkZmFUcmFpblNbIWlzLm5hKGRmYVRyYWluUyRyZXZpZXdfc2NvcmVzX3JhdGluZyksXQpsMSA9IGwxWyFpcy5uYShsMSRob3N0X2lzX3N1cGVyaG9zdCksXQpsMSA9IGwxW2wxJGhvc3RfaXNfc3VwZXJob3N0IT0nJyxdCmwxJGhvc3RfaXNfc3VwZXJob3N0W2wxJGhvc3RfaXNfc3VwZXJob3N0ID09ICJ0Il0gPC0gIlRydWUiCmwxJGhvc3RfaXNfc3VwZXJob3N0W2wxJGhvc3RfaXNfc3VwZXJob3N0ID09ICJmIl0gPC0gIkZhbHNlIgpsMSRob3N0X2lzX3N1cGVyaG9zdCA8LSBmYWN0b3IobDEkaG9zdF9pc19zdXBlcmhvc3QpCmNvbG5hbWVzKGwxKVt3aGljaChuYW1lcyhsMSkgPT0gImhvc3RfaXNfc3VwZXJob3N0IildIDwtICJTdXBlcmhvc3QiCnN1cGVyaG9zdCA9IGdncGxvdChsMSxhZXMoaG9zdF9yZXNwb25zZV9yYXRlLHJldmlld19zY29yZXNfcmF0aW5nLGNvbG9yPVN1cGVyaG9zdCkpK2dlb21fcG9pbnQoYWxwaGEgPSAuNSxzdHJva2U9MCkgK3hsYWIoIkhvc3QgUmVzcG9uc2UgUmF0ZSIpICt5bGFiKCJBdmcgUmF0aW5ncyIpKyBnZ3RpdGxlKCJXaGF0IGRvZXMgaXQgdGFrZSB0byBiZSBhIFN1cGVyIEhvc3QiKQpzdXBlcmhvc3QgKyBnZ3RpdGxlKCJXaGF0IGRvZXMgaXQgdGFrZSB0byBiZSBhIFN1cGVyaG9zdD8iLAogICAgICAgICAgc3VidGl0bGUgPSAiQXZlcmFnZSBSYXRpbmcgYnkgUmVzcG9uc2UgUmF0ZSIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSArCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBjb2xvciA9ICJncmV5MzUiKSkgKwogIHRoZW1lKHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJncmV5NjgiKSkKYGBgCgpBaXJibmLigJlzIHNpdGUgcmVxdWlyZXMgYSBob3N0IHRvIHNhdGlzZnkgY2VydGFpbiByZXF1aXJlbWVudHMgdG8gYmUgY29uc2lkZXJlZCBhcyBhIHN1cGVyIGhvc3QuV2UgY29uc2lkZXIgdHdvIHBhcmFtZXRlcnMgZnJvbSB0aGUgZGF0YXNldCBhbmQgbWVhc3VyZSB0aGUgcGVyZm9ybWFuY2U6IOKAnFJlc3BvbnNlIHJhdGXigJ0gYW5kIOKAnFJhdGluZ3PigJ0gd2hpY2ggcmFuZ2UgZnJvbSAwIHRvIDEwMC4KClRoZSBzY2F0dGVyIHBsb3QgZ2l2ZXMgYSBmZXcgaW50ZXJlc3RpbmcgaW5zaWdodHMuIFdoaWxlIG1vc3Qgc3VwZXItaG9zdHMgYXJlIGluIHRoZSByZWdpb24gb2YgaGlnaC1yYXRpbmc6aGlnaC1yZXNwb25zZS1yYXRlIHJlZ2lvbiwgd2UgY2FuIGFsc28gc2VlIGEgZmV3IGhvc3RzIHdpdGggcmVzcG9uc2UgcmF0ZXMgbGVzcyB0aGFuIDc1JSB3aGljaCB2aW9sYXRlcyB0aGUgOTAlKyBjcml0ZXJhIHNldCBieSBBaXJibmIgYWx0aG91Z2ggdGhlcmUgYXJlIGEgdmVyeSBzbWFsbCBmcmFjdGlvbiBvZiB0aGUgaG9zdHMuIEluIHJlZ2FyZCB0byBSYXRpbmdzLCBhbG1vc3QgYWxsIGhvc3RzIGFyZSByYXRlZCA3NSUgYW5kIGFib3ZlIHdpdGggYSB2ZXJ5IGZldyBiZWxvdyA3NSUuCk1vc3QgQWlyYm5iIGhvc3RzIG5lZWQgdG8gbGllIGluIHRoZSByZWdpb24gb2YgaGlnaC1yYXRpbmc6aGlnaC1yZXNwb25zZSByZWdpb24gdG8gYmUgY29uc2lkZXJlZCBhIHN1cGVyIGhvc3QsIGJ1dCBvbmx5IGEgc21hbGwgZnJhY3Rpb24gY2FuIGJlIHN1cGVyIGhvc3RzIGlmIHRoZSBjb25kaXRpb25zIGFyZSBub3Qgc2F0aXNmaWVkLgoKIyMgUHJvcGVydHkgVHlwZXMgaW4gRWFjaCBCb3JvdWdoOgoKYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnByb3BlcnR5ZGYgPC0gIGRmYVRyYWluRHVwICU+JSBncm91cF9ieShib3JvdWdoLCBwcm9wZXJ0eV90eXBlKSAlPiUgc3VtbWFyaXplKEZyZXEgPSBuKCkpCnByb3BlcnR5ZGYgPC0gcHJvcGVydHlkZiAlPiUgZmlsdGVyKHByb3BlcnR5X3R5cGUgJWluJSBjKCJBcGFydG1lbnQiLCJIb3VzZSIsIkNvbmRvbWluaXVtIiwiVG93bmhvdXNlIiwgIkxvZnQiLCJHdWVzdCBzdWl0ZSIpKQp0b3RhbHByb3BlcnR5PC0gIGRmYVRyYWluRHVwICU+JSBmaWx0ZXIocHJvcGVydHlfdHlwZSAlaW4lIGMoIkFwYXJ0bWVudCIsIkhvdXNlIiwiQ29uZG9taW5pdW0iLCJUb3duaG91c2UiLCAiTG9mdCIsIkd1ZXN0IHN1aXRlIikpJT4lIGdyb3VwX2J5KGJvcm91Z2gpICU+JSBzdW1tYXJpemUoc3VtID0gbigpKQoKcHJvcGVydHlyYXRpbyA8LSBtZXJnZShwcm9wZXJ0eWRmLCB0b3RhbHByb3BlcnR5LCBieT0iYm9yb3VnaCIpCnByb3BlcnR5cmF0aW8gPC0gcHJvcGVydHlyYXRpbyAlPiUgbXV0YXRlKHJhdGlvID0gRnJlcS9zdW0pCmdncGxvdChwcm9wZXJ0eXJhdGlvLCBhZXMoeD1ib3JvdWdoLCB5PXJhdGlvLCBmaWxsID0gcHJvcGVydHlfdHlwZSkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIsc3RhdD0iaWRlbnRpdHkiKSArIHhsYWIoIkJvcm91Z2giKSArIHlsYWIoIkNvdW50IikrCiAgc2NhbGVfZmlsbF9kaXNjcmV0ZShuYW1lID0gIlByb3BlcnR5IFR5cGUiKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICBnZ3RpdGxlKCJQcm9wZXJ0eSBUeXBlcyBpbiBOWUMiLAogICAgICAgICAgc3VidGl0bGUgPSAiTWFwIHNob3dpbmcgUGVyY2VudGFnZSBDb3VudCBvZiBQcm9wZXJ0eSBUeXBlIGJ5IEJvcm91Z2ggIikgKwogICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSkgKwogICAgICAgICAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBjb2xvciA9ICJncmV5MzUiKSkgKwogICAgICAgICAgdGhlbWUocGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImdyZXk2OCIpKStzY2FsZV9jb2xvcl9ncmFkaWVudChsb3c9IiNkM2NiY2IiLCBoaWdoPSIjODUyZWFhIikrCiAgICAgICAgICBzY2FsZV9maWxsX21hbnVhbCgiUHJvcGVydHkgVHlwZSIsIHZhbHVlcz1jKCIjZTA2ZjY5IiwiIzM1N2I4YSIsICIjN2RiNWI4IiwgIiM1OWM2ZjMiLCAiI2Y2YzQ1OCIsIiMwMEZGMDAiKSkgKwogICAgICAgICAgeGxhYigiTmVpZ2hib3Job29kIikgKyB5bGFiKCJQZXJjZW50YWdlIikKYGBgCkluIHRoZSBhYm92ZSBwbG90IHdoaWNoIHJlcHJlc2VudHMgdGhlIHBlcmNlbnRhZ2Ugb2YgcHJvcGVydHkgdHlwZXMgZm9yIGVhY2ggYm9yb3VnaCwgd2Ugb2JzZXJ2ZSBleGNlcHQgZm9yIHN0YXRlbiBpc2xhbmQsIGFwYXJ0bWVudHMgbWFrZSB1cCBmb3IgdGhlIGhpZ2hlc3QgbnVtYmVyIG9mIG9ic2VydmF0aW9uIHdoaWNoIGlzIG1vcmUgdGhhbiA1MCUuIFRoaXMgY291bGQgYmUgZHVlIHRvIHRoZSBmYWN0IHRoYXQgbW9zdCBvZiB0aGUgTmV3IHlvcmtlcnMgKGFwcHJveCA2NSUpIG93biBwcm9wZXJ0aWVzIGluIGJ1bGRpbmdzIHdpdGggbW9yZSB0aGFuIDEwIHVuaXRzIGkuZS4sIGFwYXJ0bWVudHMgKE5ZVSBGdXJtYW4gQ2VudGVyIDIwMTcpLiBUaGUgc2FtZSByZXBvcnQgZnJvbSAgTllVIEZ1cm1hbiBjZW50ZXIgYWxzbyBleHBsYWlucyB0aGUgcGhlbm9tZW5vbiBpbiBzdGF0ZW4gaXNsYW5kIHdoZXJlIHRoZXJlIGFyZSBsZXNzIG51bWJlciBvZiBhcGFydG1lbnRzIGFuZCBtb3JlIG51bWJlciBvZiBob3VzZXMuIFRoaXMgaXMgZHVlIHRvIHRoZSBmYWN0IHRoYXQgc3RhdGVuIGlzbGFuZCBoYXMgYXBwcm94aW1hdGVseSA2MCUgb2YgdGhlIHByb3BlcnRpZXMgYXMgaG91c2VzIGFuZCBqdXN0IG92ZXIgMTUlIGFwYXJ0bWVudHMuCgpDaG9yb3BsZXRociBNYXAgZm9yIExvY2F0aW9uIFJldmlldyBSYXRpbmc6CgpgYGB7cn0KbGlicmFyeShkZXZ0b29scykKI2luc3RhbGxfZ2l0aHViKCdhcmlsYW1zdGVpbi9jaG9yb3BsZXRoclppcEB2MS41LjAnKQojIEFjY2Vzc2luZyBjb3VudHlzIGZpbGUgdG8gZ2V0IHJlZ2lvbiBvZiBlYWNoIHByb3BlcnR5CmRmemlwIDwtIHJlYWRfY3N2KCJOWUNvdW50eVppcC5jc3YiKQpkZnppcCR6aXBjb2RlIDwtIGFzLmNoYXJhY3RlcihkZnppcCR6aXBjb2RlKQpza2ltKGRmemlwKQpgYGAKCk1lcmdpbmcgdGhlIE5ldyBZb3JrIGRhdGFzZXQgd2l0aCBjb3VudHkgZmlsZToKCmBgYHtyfQpkZmFUcmFpbkR1cDIgPC0gbWVyZ2UoZGZhVHJhaW5EdXAsZGZ6aXAsYnkueD0iemlwY29kZSIsYnkueSA9ICJ6aXBjb2RlIixhbGwueD0gVFJVRSkgCmRmYVRyYWluRHVwIDwtIGRmYVRyYWluRHVwMgpgYGAKCkNoZWNraW5nIHRoZSBjb2x1bW5zIHJlcXVpcmVkIGZvciBwbG90dGluZyBhIGNob3JvcGxldGhyIG1hcDoKCmBgYHtyfQpkYXRhKGNvdW50eS5yZWdpb25zLCAKICAgICBwYWNrYWdlID0gImNob3JvcGxldGhyTWFwcyIpCnJlZ2lvbiA8LSBjb3VudHkucmVnaW9ucyAlPiUKICBmaWx0ZXIoc3RhdGUubmFtZSA9PSAibmV3IHlvcmsiKQoKcmVnaW9uCmBgYAoKSm9pbmluZyB0aGUgcmVnaW9uIGRhdGEgZm9yIGNob3JvcGxldGhyIG1hcCB3aXRoIG91ciBkYXRhOgoKYGBge3J9CnBsb3RkYXRhIDwtIGlubmVyX2pvaW4oZGZhVHJhaW5EdXAsIAogICAgICAgICAgICAgICAgICAgICAgIHJlZ2lvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgYnk9YygicmVnaW9uIiA9ICJyZWdpb24iKSkKCmBgYAoKUGxvdHRpbmcgdGhlIGNob3JvcGxldGhyIG1hcDoKCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNob3JvcGxldGhyKQojIFdoaWNoIGxvY2F0aW9ucyBoYXZlIGJldHRlciByYXRpbmdzPwp6aXBSZXZpZXdzIDwtIHBsb3RkYXRhICAlPiUgZ3JvdXBfYnkocmVnaW9uKSAlPiUgc3VtbWFyaXNlKGF2Z19sb2NfcmV2aWV3ID0gbWVhbihyZXZpZXdfc2NvcmVzX2xvY2F0aW9uLCBuYS5ybSA9IFRSVUUpKQpjb2xuYW1lcyh6aXBSZXZpZXdzKSA8LSBjKCJyZWdpb24iLCJ2YWx1ZSIpCiN6aXBSZXZpZXdzJHJlZ2lvbiA8LSBhcy5jaGFyYWN0ZXIoemlwUmV2aWV3cyRyZWdpb24pCiNueWNfZmlwcyA9IGMoMzYwMDUsMzYwMDEsMzYwMDMsMzYwMDcsMzYwMTEsMzYwMTMsMzYwMTksMzYwMjEsMzYwMjMsMzYwMjUsMzYwMjcsMzYwMjkpCgpnX2xvY2F0aW9ucyA8LSBjb3VudHlfY2hvcm9wbGV0aCh6aXBSZXZpZXdzLHN0YXRlX3pvb20gPSAibmV3IHlvcmsiLHRpdGxlID0gIkxvY2F0aW9uIFJldmlldyBTY29yZXMgYnkgUmVnaW9uIiwKbGVnZW5kID0gIkF2ZXJhZ2UgU2NvcmUiKSArIGdndGl0bGUoIldoaWNoIGFyZWEgaXMgdGhlIGJlc3Q/IiwKICAgICAgICAgIHN1YnRpdGxlID0gIk1hcCBzaG93aW5nIEF2ZXJhZ2UgTG9jYXRpb24gU2NvcmUgYnkgQXJlYSIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSArCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBjb2xvciA9ICJncmV5MzUiKSkgKwogIHRoZW1lKHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJncmV5NjgiKSkrc2NhbGVfY29sb3JfZ3JhZGllbnQobG93PSIjZDNjYmNiIiwgaGlnaD0iIzg1MmVhYSIpKyBzY2FsZV9maWxsX2JyZXdlcigiTG9jYXRpb24gUmV2aWV3IFNjb3JlIixwYWxldHRlPTMpCmdncGxvdGx5KGdfbG9jYXRpb25zKQojY291bnR5X3pvb20gPSBueWNfZmlwcwojc3RhdGVfem9vbSA9ICJuZXcgeW9yayIKYGBgCgpUaGUgbWFwIGFib3ZlIHJlcHJlc2VudHMgdGhlIGxvY2F0aW9uIHJldmlld3MgYmFzZWQgb24gY291bnRpZXMuIFdlIG9ic2VydmUgdGhhdCBDbGludG9uIGNvdW50eSBoYXMgdGhlIGhpZ2hlc3QgbG9jYXRpb24gcmV2aWV3IGZvbGxvd2VkIGJ5IEFsYmFueSwgYW5kIHRoZW4gRGVsYXdhcmUuCgpGaW5kaW5nIG5lYXJlc3Qgc3Vid2F5IFN0YXRpb24gYW5kIG51bWJlciBvZiBzdGF0aW9ucyBuZWFyIHRvIHRoZSBwcm9wZXJ0eToKCmBgYHtyfQpkZnppcFMkU3RhdGlvbiA9IHBhc3RlKGRmemlwUyRMaW5lLCBkZnppcFMkU3RhdGlvbm5hbWUsIHNlcD0iLSIpCk5ZQ1N1Yl9TdGF0aW9uczwtYWdncmVnYXRlKGRmemlwU1ssIDM6NF0sIGxpc3QoZGZ6aXBTJFN0YXRpb24pLCBtZWFuKQpjb2xuYW1lcyhOWUNTdWJfU3RhdGlvbnMpWzFdIDwtICJTdGF0aW9uIgpgYGAKYGBge3J9CmRmYVRyYWluRHVwJGxhdF9kZWM8LWZvcm1hdChyb3VuZChkZmFUcmFpbkR1cCRsYXRpdHVkZSwgMyksIG5zbWFsbCA9IDMpCgpkZmFUcmFpbkR1cCRsb25fZGVjPC1mb3JtYXQocm91bmQoZGZhVHJhaW5EdXAkbG9uZ2l0dWRlLCAzKSwgbnNtYWxsID0gMykKCmRmYVRyYWluRHVwJGxhdF9sb24gPSBwYXN0ZShkZmFUcmFpbkR1cCRsYXRfZGVjLCBkZmFUcmFpbkR1cCRsb25fZGVjLCBzZXA9Il8iKQoKTllDQm9yX1Nob3J0PC1kZmFUcmFpbkR1cCU+JXNlbGVjdChsYXRfbG9uLGxhdF9kZWMsIGxvbl9kZWMpCgpOWUNCb3Jfc2hvcnRfZHVwPC1OWUNCb3JfU2hvcnRbIWR1cGxpY2F0ZWQoTllDQm9yX1Nob3J0KSwgXQoKTllDQm9yX3Nob3J0X2R1cCRzaG9ydGVzdF9tZXRybz1OQQoKTllDQm9yX3Nob3J0X2R1cCRudW1iZXJfbGVzc190aGFuXzE9TkEKYGBgCgpDb2RlIGZvciBmaW5kaW5nIHRoZSBuZWFyZXN0IG1ldHJvIHN0YXRpb24gYW5kIG51bWJlciBvZiBtZXRybyBzdGF0aW9ucyB3aXRoaW4gYSBtaWxlOgoKYGBge3J9CiMgTllDQm9yX3Nob3J0X2R1cCRsYXRfZGVjPC1hcy5udW1lcmljKE5ZQ0Jvcl9zaG9ydF9kdXAkbGF0X2RlYykKIyBOWUNCb3Jfc2hvcnRfZHVwJGxvbl9kZWM8LWFzLm51bWVyaWMoTllDQm9yX3Nob3J0X2R1cCRsb25fZGVjKQojIAojIAojIGZvciAoaSBpbiAxOm5yb3coTllDQm9yX3Nob3J0X2R1cCkpewojICAgCiMgICAjcHJpbnQoaSkKIyAgIAojICAgc2hvcnRlc3RfZGlzdCA9IDEwMDAKIyAgIAojICAgbnVtX2xlc3NfMSA9IDAKIyAgIAojICAgZm9yIChqIGluIDE6bnJvdyhOWUNTdWJfU3RhdGlvbnMpKXsKIyAgICAgCiMgICAgZGlzdGFuY2UgPSBzcXJ0KCgoTllDQm9yX3Nob3J0X2R1cFtpLDJdLU5ZQ1N1Yl9TdGF0aW9uc1tqLDJdKSooTllDQm9yX3Nob3J0X2R1cFtpLDJdLU5ZQ1N1Yl9TdGF0aW9uc1tqLDJdKSkgKyAgICAoKE5ZQ0Jvcl9zaG9ydF9kdXBbaSwzXS1OWUNTdWJfU3RhdGlvbnNbaiwzXSkqKE5ZQ0Jvcl9zaG9ydF9kdXBbaSwzXS1OWUNTdWJfU3RhdGlvbnNbaiwzXSkpKSAqIDY5CiMgICAgCiMgICBpZiAoZGlzdGFuY2U8PTEpewojICAgICBudW1fbGVzc18xID0gbnVtX2xlc3NfMSArIDEKIyAgIH0KIyAgICAKIyAgIGlmKGRpc3RhbmNlPHNob3J0ZXN0X2Rpc3QpewojICAgICBzaG9ydGVzdF9kaXN0PWRpc3RhbmNlCiMgICB9CiMgICAgZWxzZXsKIyAgICAgIHNob3J0ZXN0X2Rpc3Q9c2hvcnRlc3RfZGlzdAojICAgIH0KIyAgIAojICAgICAKIyAgIH0KIyAgIAojICAgTllDQm9yX3Nob3J0X2R1cFtpLDRdID0gc2hvcnRlc3RfZGlzdAojICAgCiMgICBOWUNCb3Jfc2hvcnRfZHVwW2ksNV0gPSBudW1fbGVzc18xCiMgfQoKYGBgCgoKYGBge3J9CiMgZGZhVHJhaW5EdXAgPC0gbWVyZ2UoZGZhVHJhaW5EdXAsTllDQm9yX3Nob3J0X2R1cCxieS54PSJsYXRfbG9uIixieS55ID0gImxhdF9sb24iLGFsbC54PSBUUlVFKSAKYGBgCgpgYGB7cn0KZGZhVHJhaW5EdXAgPC0gcmVhZF9jc3YoIk1lZ2hhX05ZQ19EYXRhLmNzdiIpCmBgYAoKCmBgYHtyfQpkZmFUcmFpbkR1cCAlPiUgc2VsZWN0KGlkLGJvcm91Z2gsc2hvcnRlc3RfbWV0cm8sbnVtYmVyX2xlc3NfdGhhbl8xLGhpZ2hfYm9va2luZ19yYXRlKSAlPiUgYXJyYW5nZShkZXNjKG51bWJlcl9sZXNzX3RoYW5fMSkpCmBgYAoKQmFyIGNoYXJ0IG9mIHBvcHVsYXRpb24gb2YgaGlnaCBib29raW5nIHJhdGUgZm9yIHByb3BlcnRpZXMgd2l0aCBtZXRybyBzdGF0aW9ucyB3aXRoaW4gYSBtaWxlOgoKYGBge3J9CmRmYVRyYWluRHVwUGxvdE1ldHJvIDwtIGRmYVRyYWluRHVwICU+JSBncm91cF9ieShudW1iZXJfbGVzc190aGFuXzEpJT4lc3VtbWFyaXplKEZyZXEgPSBtZWFuKGhpZ2hfYm9va2luZ19yYXRlKSkgJT4lIAogIGdncGxvdCggYWVzKHggPSBudW1iZXJfbGVzc190aGFuXzEsIHkgPSBGcmVxKSkrCiAgZ2VvbV9iYXIoIHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkrCiAgZ2d0aXRsZSgiUHJvcG9ydGlvbiBvZiBIaWdoIEJvb2tpbmcgUmF0ZSBieSBOdW1iZXIgb2YgbWV0cm8gc3RhdGlvbnMgd2l0aGluIGEgbWlsZSIpICsKICAgICAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpICsKICAgICAgICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgY29sb3IgPSAiZ3JleTM1IikpICsKICAgICAgICAgIHhsYWIoIkNvdW50IG9mIG1ldHJvIHN0YXRpb25zIHdpdGhpbiBhIG1pbGUgZm9yIHByb3BlcnRpZXMiKSArIHlsYWIoIkhpZ2ggQm9va2luZyByYXRlIFByb3BvcnRpb24iKQpkZmFUcmFpbkR1cFBsb3RNZXRybwpgYGAKYGBge3J9CmRmX3RvdGFsID0gZGF0YS5mcmFtZSgpCmZvciAodmFsIGluIDA6bWF4KGRmYVRyYWluRHVwJG51bWJlcl9sZXNzX3RoYW5fMSkpewogIHg8LWRmYVRyYWluRHVwICU+JSBmaWx0ZXIobnVtYmVyX2xlc3NfdGhhbl8xPD12YWwpICU+JSBzdW1tYXJpemUobWVhbmZvcmxlc3Nlcj1tZWFuKGhpZ2hfYm9va2luZ19yYXRlKSkKICB5PC0gZGZhVHJhaW5EdXAgJT4lIGZpbHRlcihudW1iZXJfbGVzc190aGFuXzE+dmFsKSAlPiUgc3VtbWFyaXplKG1lYW5mb3JncmVhdGVyPW1lYW4oaGlnaF9ib29raW5nX3JhdGUpKQogICNwcmludChjKHZhbCx4JG1lYW4sIHkkbWVhbikpCiAgIGRmIDwtIGRhdGEuZnJhbWUodmFsLHgseSkKICBkZl90b3RhbCA8LSByYmluZChkZl90b3RhbCxkZikKfQpkZl90b3RhbApgYGAKClRoaXMgYmFyIGdyYXBoIGFuZCB0aGUgYWJvdmUgcmVzdWx0c2V0IHNob3dzIHRoYXQgdGhlIGJvb2tpbmcgcmF0ZSBvZiBwcm9wZXJ0eSBkZWNyZWFzZXMgYXMgdGhlIG51bWJlciBvZiBtZXRybyBzdGF0aW9ucyB3aXRoaW4gYSBtaWxlIGluY3JlYXNlcy4gVGhpcyBwaGVub21lbm9uIHMgb2JzZXJ2ZWQgdW50aWwgdGhlIG51bWJlciBvZiBzdGF0aW9ucyBpcyAxOS4gQnV0IHdlIGFsc28gb2JzZXJ2ZSB0aGF0IDE5IGlzIHRoZSBpbmZsZWN0aW9uIHBvaW50IGFmdGVyIHdoaWNoIHRoZSB0cmVuZCByZXZlcnNlcyBpLmUuLCBib29raW5nIHJhdGUgaW5jcmVhc2VzIHdpdGggYW4gaW5jcmVhc2UgaW4gbnVtYmVyIG9mIG1ldHJvIHN0YXRpb25zIHdpdGhpbiAxIG1pbGUuIFRoZSBmaXJzdCBwaGVub21lbm9uIGNvdWxkIGJlIGR1ZSB0byBwZW9wbGUgaW4gdGhvc2UgcmVnaW9ucyBwcmVmZXIgYSBsb3dlciBwcmljZSBvZiB0aGUgcHJvcGVydHkgdGhhbiBjb21mb3J0IGZvciB0cmFuc2l0aW5nLiBUaGUgcGhlbm9tZW5vbiByZXZhcnNhbCBjb3VsZCBkdWUgdG8gdGhlIGZhY3QgdGhhdCBwZW9wbGUgbWlnaHQgYmUgbG9va2luZyBmb3IgY29tZm9ydCBmb3IgdHJhbnNpdGluZyB0aGFuIHRoZSBoaWdoZXIgcHJpY2UgdG8gYmUgcGFpZCBmb3IgdGhlIHByb3BlcnR5LiBNb3JlIG51bWJlciBvZiBtZXRybyBzdGF0aW9ucyB3aXRoaW4gYSBtaWxlIG1heSBiZSBtb3N0bHkgc2VlbiAgYXJvdW5kIE1hbmhhdHRhbiBhbmQgQnJvb2tseW4gd2hlcmUgcGVvcGxlIG1pZ2h0IHZpc2l0IHRoZSBwbGFjZXMgZm9yIGJ1c2luZXNzIHB1cnBvc2VzIGFuZCBoYXZlIHRoZSBuZWVkIHRvIHNodXR0bGUgbW9yZSBvZnRlbiB3aXRoaW4gTllDLgoKCkJhciBncmFwaCBmb3IgY291bnQgb2YgbWV0cm8gc3RhdGlvbnMgd2l0aGluIGEgbWlsZSBmb3IgcHJvcGVydGllczoKCmBgYHtyfQpkZmFUcmFpbkR1cCRzdGF0aW9uc2luYW1pbGUxIDwtIGN1dCh4PWRmYVRyYWluRHVwJG51bWJlcl9sZXNzX3RoYW5fMSwgYnJlYWtzPXNlcShmcm9tPS0xLCB0bz1jZWlsaW5nKG1heChkZmFUcmFpbkR1cCRudW1iZXJfbGVzc190aGFuXzEpKSwgYnkgPSA1KSkKYGBgCmBgYHtyfQpkZmFUcmFpbkR1cFBsb3RNZXRybzEgPC0gZGZhVHJhaW5EdXAgJT4lIGdyb3VwX2J5KHN0YXRpb25zaW5hbWlsZTEsaGlnaF9ib29raW5nX3JhdGUpJT4lc3VtbWFyaXplKEZyZXEgPSBuKCkpICU+JSAKICBnZ3Bsb3QoIGFlcyh4ID0gc3RhdGlvbnNpbmFtaWxlMSwgeSA9IEZyZXEsZmlsbCA9IGFzLmZhY3RvcihoaWdoX2Jvb2tpbmdfcmF0ZSkpKSsKICBnZW9tX2Jhciggc3RhdCA9ICJpZGVudGl0eSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSsKICAgIGdndGl0bGUoIkNvdW50IG9mIFByb3BlcnRpZXMgYnkgbnVtYmVyIG9mIG1ldHJvIHN0YXRpb25zIHdpdGhpbiBhIG1pbGUiKSArCiAgICAgICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSArCiAgICAgICAgICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGNvbG9yID0gImdyZXkzNSIpKSArCiAgICAgICAgICB4bGFiKCJOdW1iZXIgb2YgbWV0cm8gc3RhdGlvbnMgd2l0aGluIGEgbWlsZSIpICsgeWxhYigiQ291bnQgb2YgcHJvcGVydGllcyIpCmRmYVRyYWluRHVwUGxvdE1ldHJvMQpgYGAKCmBgYHtyfQpkZmFUcmFpbiA8LSBtZXJnZShkZmFUcmFpbixkZmFUcmFpbkR1cCAlPiUgc2VsZWN0KGlkLCByZWNvZGUyKSwgYnkueD0iaWQiLCBieS55ID0gImlkIixhbGwueD0gVFJVRSkgCmBgYApgYGB7cn0KZGYgPC0gZGZhVHJhaW4gJT4lIG11dGF0ZShzdGF0aW9uc19pbl9hX21pbGUgPSBhcy5mYWN0b3IocmVjb2RlMikpCmBgYAoKVGhpcyBiYXIgZ3JhcGggaW5kaWNhdGVzIHRoYXQgYXMgdGhlIG51bWJlciBvZiBwcm9wZXJ0aWVzIGRlcmVhc2VzIHdpdGggYW4gaW5jcmVhc2UgaW4gbnVtYmVyIG9mIG1ldHJvIHN0YXRpb25zIHdpdGhpbiBhIG1pbGUgb2YgdGhlIHByb3BlcnR5LiBTdWJzZXF1ZW50bHksIHdlIG9ic2VydmUgYSBkZWNyZWFzZSBpbiBoaWdoIGJvb2tpbmcgcmF0ZS4KCgojIyMjIFJFU1VMVFMgQU5EIEZJTkRJTkdTCgpUaGUgaW5zaWdodHMgYWJvdXQgdGhlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgYW5kIG5vbi1zaWduaWZpY2FudCB2YXJpYWJsZXMgaGVscGVkIHVzIHRvIGRlY2lkZSB3aGF0IHZhcmlhYmxlcyB0byBpbmNsdWRlIGluIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGZvciB0aGUgTllDIGRhdGEuCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQpkZmFUcmFpbiA8LSBzYW1wbGVfZnJhYyhkZiwgMC43NSkKZGZhVGVzdCA8LSBkcGx5cjo6c2V0ZGlmZihkZiwgZGZhVHJhaW4pCmBgYAoKYGBge3J9CmNvbHNfdG9fc2VsZWN0IDwtIGMoCiJoaWdoX2Jvb2tpbmdfcmF0ZSIsCiJcIjI0LWhvdXIgY2hlY2staW5cIiIsCiJcIkFpciBjb25kaXRpb25pbmdcIiIsCiJcIkJlZCBsaW5lbnNcIiIsCiJcIkNhYmxlIFRWXCIiLAoiXCJDbGVhbmluZyBiZWZvcmUgY2hlY2tvdXRcIiIsCiJcIkNvZmZlZSBtYWtlclwiIiwKIlwiRGlzaGVzIGFuZCBzaWx2ZXJ3YXJlXCIiLAoiXCJGYW1pbHkva2lkIGZyaWVuZGx5XCIiLAoiXCJGcmVlIHN0cmVldCBwYXJraW5nXCIiLAoiXCJIYWlyIGRyeWVyXCIiLAoiXCJIb3N0IGdyZWV0cyB5b3VcIiIsCiJcIkhvdCB3YXRlclwiIiwKIlwiTGFwdG9wIGZyaWVuZGx5IHdvcmtzcGFjZVwiIiwKIlwiTHVnZ2FnZSBkcm9wb2ZmIGFsbG93ZWRcIiIsCiJcIk5vIHN0YWlycyBvciBzdGVwcyB0byBlbnRlclwiIiwKIlwiUGFpZCBwYXJraW5nIG9mZiBwcmVtaXNlc1wiIiwKIlwiUGV0cyBhbGxvd2VkXCIiLAoiXCJQZXRzIGxpdmUgb24gdGhpcyBwcm9wZXJ0eVwiIiwKIlwiU2FmZXR5IGNhcmRcIiIsCiJcIlNlbGYgY2hlY2staW5cIiIsCiJcIlNtb2tlIGRldGVjdG9yXCIiLAoiRGlzaHdhc2hlciIsCiJEb29ybWFuIiwKIkVsZXZhdG9yIiwKIkd5bSIsCiJIZWF0aW5nIiwKIkludGVybmV0IiwKIklyb24iLAoiS2l0Y2hlbiIsCiJNaWNyb3dhdmUiLAoiT3ZlbiIsCiJUViIsCiJXYXNoZXIiLAoicHJvcGVydHlfdHlwZSIsCiJob3N0X2lzX3N1cGVyaG9zdCIsCiJyb29tX3R5cGUiLAoicmV2aWV3X3Njb3Jlc19jaGVja2luIiwKInJldmlld19zY29yZXNfbG9jYXRpb24iLAoicmV2aWV3X3Njb3Jlc192YWx1ZSIsCiJib3JvdWdoIiwKInByaWNlIiwKImFjY2VzcyIsCiJob3N0X3Jlc3BvbnNlX3RpbWUiLAoiY2xlYW5pbmdfZmVlIiwKImhvc3Rfc2luY2UiLAoiaG91c2VfcnVsZXMiLAoibWluaW11bV9uaWdodHMiLAoiaW5zdGFudF9ib29rYWJsZSIsCiJzdGF0aW9uc19pbl9hX21pbGUiCikKCmRmYVRyYWluIDwtIGRmYVRyYWluICU+JSAKICBzZWxlY3QoY29sc190b19zZWxlY3QpCmRmYVRyYWluIDwtIGRmYVRyYWluICU+JSAKICBtdXRhdGUoaGlnaF9ib29raW5nX3JhdGUgPSBhcy5mYWN0b3IoaGlnaF9ib29raW5nX3JhdGUpKQpgYGAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KZ2xtTW9kZWwgPC0gdHJhaW4oaGlnaF9ib29raW5nX3JhdGUgfiAgLiwKICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gJ2Jpbm9taWFsJywKICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImdsbSIsCiAgICAgICAgICAgICAgICAgIGRhdGEgPSBkZmFUcmFpbiAlPiUgZHJvcF9uYSgpKQpgYGAKCmBgYHtyfQpzdW1tYXJ5KGdsbU1vZGVsKQpgYGAKCmBgYHtyfQpleHAoY29lZihnbG0oaGlnaF9ib29raW5nX3JhdGUgfiAuLCBmYW1pbHkgPSAiYmlub21pYWwiLCBkYXRhID0gZGZhVHJhaW4gJT4lIGRyb3BfbmEoKSkpKQpgYGAKCmBgYHtyfQpza2ltKGRmYVRyYWluKQpgYGAKCgpgYGB7cn0KcmVzdWx0c0dMTSA8LQogIGdsbU1vZGVsICU+JQogIHByZWRpY3QoZGZhVGVzdCAlPiUgIGRyb3BfbmEoKSAsIHR5cGUgPSAncHJvYicpICU+JSAKICBiaW5kX2NvbHMoZGZhVGVzdCAlPiUgIGRyb3BfbmEoKSAsIHByZWRpY3RlZENsYXNzID0gLikgJT4lCiAgbXV0YXRlKHByZWRpY3RlZENsYXNzID0gYXMuZmFjdG9yKGlmZWxzZShgMWAgPiAwLjYgLCAxLCAwKSkpCgpyZXN1bHRzR0xNICU+JQogIHh0YWJzKH5wcmVkaWN0ZWRDbGFzcytoaWdoX2Jvb2tpbmdfcmF0ZSwgLikgJT4lCiAgY29uZnVzaW9uTWF0cml4KHBvc2l0aXZlID0gJzEnKQpgYGAKCldlIGRldmVsb3BlZCBvdXIgYnVzaW5lc3MgY2FzZSBmb2N1c2luZyBvbiBhdm9pZGluZyB0aGUgbmVnYXRpdmUgY29uc2VxdWVuY2VzIG9mIGludmVzdGluZyBpbiBwcm9wZXJ0aWVzIHRoYXQgd291bGQgeWllbGQgbG93IGJvb2tpbmcgcmF0ZXMuIFRoZXJlZm9yZSwgd2Ugd2FudGVkIHRvIG1pbmltaXplIHRoZSBGYWxzZSBQb3NpdGl2ZSBSYXRlIChGUFIpIC4gV2UgaW5jcmVhc2VkIHRoZSBzcGVjaWZpY2l0eS4gVGhlIGN1dG9mZiBvZiAwLjYgcmVkdWNlZCB0aGUgbnVtYmVyIG9mIGZhbHNlIHBvc2l0aXZlcy4gCldlIGhhZCB0byBtYWtlIGEgdHJhZGVvZmYgc2luY2UgZGVjcmVhc2luZyBmYWxzZSBwb3NpdGl2ZXMgaW5jcmVhc2VkIHRoZSBmYWxzZSBuZWdhdGl2ZXMgYXMgd2VsbC4gSG93ZXZlciwgZm9yIGEgcHJvcGVydHkgYmFzZWQgaW4gTlkgd2hpY2ggY2FuIGJlIGV4cGVuc2l2ZSwgZmFsc2UgcG9zaXRpdmVzIG9yIGluY29ycmVjdGx5IGNsYXNzaWZ5aW5nIGEgcHJvcGVydHkgd2l0aCBhIGxvdyBib29raW5nIHJhdGUgYXMgb25lIHdpdGggYSBoaWdoIGJvb2tpbmcgcmF0ZSB3b3VsZCBoYXZlIGEgZ3JlYXRlciByaXNrIGFzc29jaWF0ZWQgd2l0aCBpdCB0byB0aGUgaW52ZXN0b3IuCgoKV2UgaGF2ZSAyIHBhcnQgcmVjb21tZW5kYXRpb25zIHRvIGFjaGlldmUgaGlnaCBib29raW5nIHJhdGVzLiBUaGUgZmlyc3QgaXMgYWJvdXQgcHJvZHVjdHMgdG8gYmUgcHVyY2hhc2VkIG9yIGZhY3RvcnMgdG8gYmUgY29uc2lkZXJlZCBiZWZvcmUgYnV5aW5nIHRoZSBwcm9wZXJ0eS4gVGhlIHNlY29uZCBzZXQgZm9jdXNlcyBvbiBtYWludGVuYW5jZSBhbmQgbWFya2V0aW5nIG9mIHRoZSBwcm9wZXJ0eSBhbG9uZyB3aXRoIHNlcnZpY2VzIHRvIGJlIG9mZmVyZWQgdG8gdGhlIGd1ZXN0cyBkdXJpbmcgdGhlaXIgc3RheS4gV2hpbGUgdGhlIGZpcnN0IHBhcnQgaXMgaW1wb3J0YW50LCB3ZSBvYnNlcnZlIHRoYXQgbW9yZSBlbXBoYXNpcyBzaG91bGQgYmUgbGFpZCBvbiBzZXJ2aWNlcyBvZmZlcmVkIGFmdGVyIHB1cmNoYXNpbmcgdGhlIHByb3BlcnR5LgoKMS4gUmVjb21tZW5kYXRpb25zIEJlZm9yZSBCdXlpbmcgdGhlIFByb3BlcnR5CiAgICBhKSBJbnZlc3QgaW4gYSBzbWFydGJveCBvciBrZXlwYWQgdG8gZmFjaWxpdGF0ZSBlYXN5IHNlbGYgMjQgaG91ciBjaGVjay1pbiAKICAgIGIpIEJ1eSBwcm9wZXJ0aWVzIHdpdGggZnJlZSBwYXJraW5nCiAgICBjKSBQcm92aWRlIGFjY2VzcyB0byBkaXNoZXMgYW5kIHNpbHZlcndhcmUKICAgIGQpIENvbnNpZGVyIGludmVzdGluZyBpbiBhIGhvdXNlIG92ZXIgYW4gYXBhcnRtZW50IG9yIGEgdG93bmhvdXNlCiAgICBlKSBEbyBub3QgcGF5IGEgcHJlbWl1bSBvbiBhIHByb3BlcnR5IHVubGVzcyBpdCBpcyBpbiBNYW5oYXR0YW4KICAgIGYpIFByaXZhdGUgcm9vbXMgYXJlIHByZWZlcnJlZCBvdmVyIHNoYXJlZCByb29tcwogIAoyLiBSZWNvbW1lbmRhdGlvbnMgQWZ0ZXIgQnV5aW5nIHRoZSBQcm9wZXJ0eQogICAgYSkgVGhlIGludmVzdG9yIHNob3VsZCBmb2N1cyBvbiBwcm92aWRpbmcgYSBob21lIGxpa2UgZmVlbCBhbmQgZXhwZXJpZW5jZSB0byB0aGUgZ3Vlc3RzIGFzIG9wcG9zZWQgdG8ganVzdCByZW50aW5nIG91dCBhIHByb3BlcnR5CiAgICBiKSBQcm9wZXJseSBzdGF0aW5nIGFsbCBlc3NlbnRpYWwgc2VydmljZXMgbGlrZSBpbnRlcm5ldCBhbmQgaGVhdGluZywgZGVzY3JpYmluZyBlYWNoIGFzcGVjdCBvZiB0aGUgcHJvcGVydHkgc3VjaCBhcyB3aGF0IGFyZWFzIGFyZSBhY2Nlc3NpYmxlIGV0Yywgd2lsbCBiZSBhcHByZWNpYXRlZCBieSB0aGUgZ3Vlc3RzIAogICAgYykgVGhlIGludmVzdG9yIGNvdWxkIGNvbnNpZGVyIGhpcmluZyBhIGZ1bGwgdGltZSBlbXBsb3llZSB3aG8gZ3JlZXRzIGFuZCBpbnRlcmFjdHMgd2VsbCB3aXRoIHRoZSBob3N0cy4gIAogICAgZCkgVGhlIGhvc3TigJlzIHJlc3BvbnNlcyBzaG91bGQgYmUgcHJvbXB0LCBwcmVmZXJhYmx5IHdpdGhpbiBvbmUgaG91cgogICAgZSkgVGhlIGhvc3Qgc2hvdWxkIGFpbSB0byBhY2hpZXZlIHN1cGVyaG9zdCBzdGF0dXMKICAgIGYpIFNwZWNpYWwgZW1waGFzaXMgc2hvdWxkIGJlIGxhaWQgb24gYXBwcm9wcmlhdGVseSBhZHZlcnRpc2luZy9tYXJrZXRpbmcgdGhlIGF2YWlsYWJsZSBzZXJ2aWNlcyBvbiB0aGUgbGlzdGluZwogICAgZykgVGhlIGZ1bGwgdGltZSBlbXBsb3llZSBzaG91bGQgYmUgcmVzcG9uc2libGUgZm9yIG1hbmFnaW5nIHRoZSBwcm9wZXJ0eSBhbmQgcHJvdmlkZSBjb21wbGV0ZSBzdXBwb3J0IHRvIHRoZSBndWVzdHMgCiAgICBoKSBUaGUgcHJvcGVydHkgc2hvdWxkIGJlIGZhbWlseSBmcmllbmRseQoKICAKVGhpcyBraW5kIG9mIGN1c3RvbWl6ZWQgZXhwZXJpZW5jZSBpbiB0dXJuIHdpbGwgdHJhbnNsYXRlIGludG8gaGlnaCBib29raW5nIHJhdGVzIGFuZCBpbmNyZWFzZWQgcHJvZml0cy4KCiMjIyMgUkVGRVJFTkNFUwoKICAgIFsxXSAgaHR0cHM6Ly93d3cuY2l0eWxhYi5jb20vZXF1aXR5LzIwMTgvMDMvd2hhdC1haXJibmItZGlkLXRvLW5ldy15b3JrLWNpdHkvNTUyNzQ5LzQ1YWQ4YTk0MWE1YQogICAgWzJdICBodHRwczovL3NtYXJ0YXNzZXQuY29tL21vcnRnYWdlL3doZXJlLWRvLWFpcmJuYi1ob3N0cy1tYWtlLXRoZS1tb3N0LW1vbmV5IAogICAgWzNdICBodHRwczovL255LmN1cmJlZC5jb20vMjAxOS8xMi8xMy8yMTAwOTg3Mi9ueWMtaG9tZS12YWx1ZS0yMDEwcy1tYW5oYXR0YW4tYXBhcnRtZW50cwoKCgoKCgoKCgo=